Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/RealtimeServer/scriptureforge/models/training-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ export interface TrainingData extends ProjectData {
mimeType: string;
skipRows: number;
title: string;
deleted?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,8 @@ class TestEnvironment {
fileUrl: 'project01/user01_file01.csv?t=123456789123456789',
mimeType: 'text/csv',
skipRows: 0,
title: 'Test File'
title: 'Test File',
deleted: false
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export class TrainingDataService extends SFProjectDataService<TrainingData> {
},
title: {
bsonType: 'string'
},
deleted: {
bsonType: 'bool'
}
},
additionalProperties: false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ export class ConfirmSourcesComponent {
)
.pipe(quietTakeUntilDestroyed(this.destroyRef, { logWarnings: false }))
.subscribe(() => {
this.trainingDataFiles = this.trainingDataQuery?.docs.map(doc => doc.data).filter(d => d != null) ?? [];
this.trainingDataFiles =
(this.trainingDataQuery?.docs
.map(doc => doc.data)
.filter(d => d != null && d.deleted !== true) as TrainingData[]) ?? [];
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { MatCheckboxHarness } from '@angular/material/checkbox/testing';
import { By } from '@angular/platform-browser';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { createTestProjectProfile } from 'realtime-server/lib/esm/scriptureforge/models/sf-project-test-data';
import { getTrainingDataId, TrainingData } from 'realtime-server/lib/esm/scriptureforge/models/training-data';
import { BehaviorSubject, of } from 'rxjs';
import { anything, mock, verify, when } from 'ts-mockito';
import { ActivatedProjectService } from 'xforge-common/activated-project.service';
Expand All @@ -16,11 +17,13 @@ import { UserDoc } from 'xforge-common/models/user-doc';
import { NoticeService } from 'xforge-common/notice.service';
import { OnlineStatusService } from 'xforge-common/online-status.service';
import { provideTestRealtime } from 'xforge-common/test-realtime-providers';
import { TestRealtimeService } from 'xforge-common/test-realtime.service';
import { configureTestingModule, getTestTranslocoModule } from 'xforge-common/test-utils';
import { UserService } from 'xforge-common/user.service';
import { ParatextProject } from '../../../core/models/paratext-project';
import { SFProjectProfileDoc } from '../../../core/models/sf-project-profile-doc';
import { SF_TYPE_REGISTRY } from '../../../core/models/sf-type-registry';
import { TrainingDataDoc } from '../../../core/models/training-data-doc';
import { ParatextService } from '../../../core/paratext.service';
import { SFProjectService } from '../../../core/sf-project.service';
import { ProgressService, TextProgress } from '../../../shared/progress-service/progress.service';
Expand Down Expand Up @@ -1262,6 +1265,60 @@ describe('DraftGenerationStepsComponent', () => {
sendEmailOnBuildFinished: false
});
});

it('sets trainingDataFiles from realtime query results', fakeAsync(() => {
const realtimeService = TestBed.inject(TestRealtimeService);
realtimeService.addSnapshots<TrainingData>(TrainingDataDoc.COLLECTION, [
{
id: getTrainingDataId('project01', 'keep1'),
data: {
projectRef: 'project01',
dataId: 'keep1',
fileUrl: 'project01/keep1.csv',
mimeType: 'text/csv',
skipRows: 0,
title: 'keep1.csv',
ownerRef: 'user01',
deleted: false
}
},
{
id: getTrainingDataId('project01', 'skip-deleted'),
data: {
projectRef: 'project01',
dataId: 'skip-deleted',
fileUrl: 'project01/deleted.csv',
mimeType: 'text/csv',
skipRows: 0,
title: 'deleted.csv',
ownerRef: 'user01',
deleted: true
}
},
{
id: getTrainingDataId('otherProject', 'other'),
data: {
projectRef: 'otherProject',
dataId: 'other',
fileUrl: 'other/other.csv',
mimeType: 'text/csv',
skipRows: 0,
title: 'other.csv',
ownerRef: 'user02',
deleted: false
}
}
]);

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

const trainingDataFiles: Readonly<TrainingData>[] = (component as any).trainingDataFiles;
expect(trainingDataFiles.map(td => td.dataId)).toEqual(['keep1']);
}));
});

describe('confirm step', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ export class DraftGenerationStepsComponent implements OnInit {
)
.pipe(quietTakeUntilDestroyed(this.destroyRef, { logWarnings: false }))
.subscribe(() => {
this.trainingDataFiles = this.trainingDataQuery?.docs.map(doc => doc.data).filter(d => d != null) ?? [];
this.trainingDataFiles =
(this.trainingDataQuery?.docs
.map(doc => doc.data)
.filter(d => d != null && !d.deleted) as Readonly<TrainingData>[]) ?? [];
});

// Reset the field that toggles a notice that books were automatically selected
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { provideRouter } from '@angular/router';
Expand Down Expand Up @@ -31,6 +33,8 @@ describe('DraftHistoryListComponent', () => {
providers: [
provideRouter([]),
provideTestRealtime(SF_TYPE_REGISTRY),
provideHttpClient(withFetch()),
provideHttpClientTesting(),
{ provide: ActivatedProjectService, useMock: mockedActivatedProjectService },
{ provide: DraftGenerationService, useMock: mockedDraftGenerationService },
{ provide: I18nService, useMock: mockedI18nService },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,11 @@ describe('DraftSourcesComponent', () => {
env.clickLanguageCodesConfirmationCheckbox();

const savedFile = {} as TrainingData;
when(mockTrainingDataQuery.docs).thenReturn([{ data: savedFile } as TrainingDataDoc]);
const deletedFile: TrainingData = { dataId: 'deleted', deleted: true } as TrainingData;
when(mockTrainingDataQuery.docs).thenReturn([
{ data: savedFile } as TrainingDataDoc,
{ data: deletedFile } as TrainingDataDoc
]);
trainingDataQueryLocalChanges$.next();

expect(env.component.availableTrainingFiles.length).toEqual(1);
Expand All @@ -349,9 +353,11 @@ describe('DraftSourcesComponent', () => {

const savedFile1 = { dataId: 'file1' } as TrainingData;
const savedFile2 = { dataId: 'file2' } as TrainingData;
const deletedFile: TrainingData = { dataId: 'deleted', deleted: true } as TrainingData;
when(mockTrainingDataQuery.docs).thenReturn([
{ data: savedFile1 } as TrainingDataDoc,
{ data: savedFile2 } as TrainingDataDoc
{ data: savedFile2 } as TrainingDataDoc,
{ data: deletedFile } as TrainingDataDoc
]);
trainingDataQueryLocalChanges$.next();
tick();
Expand All @@ -375,7 +381,11 @@ describe('DraftSourcesComponent', () => {
when(mockedDialogService.confirm(anything(), anything(), anything())).thenResolve(true);

const savedFile = { dataId: 'saved_file', ownerRef: 'user01' } as TrainingData;
when(mockTrainingDataQuery.docs).thenReturn([{ data: savedFile } as TrainingDataDoc]);
const deletedFile: TrainingData = { dataId: 'deleted', ownerRef: 'user01', deleted: true } as TrainingData;
when(mockTrainingDataQuery.docs).thenReturn([
{ data: savedFile } as TrainingDataDoc,
{ data: deletedFile } as TrainingDataDoc
]);
trainingDataQueryLocalChanges$.next();
tick();

Expand Down Expand Up @@ -419,9 +429,11 @@ describe('DraftSourcesComponent', () => {

const initialFile1 = { dataId: 'file1' } as TrainingData;
const initialFile2 = { dataId: 'file2' } as TrainingData;
const deletedFile: TrainingData = { dataId: 'deleted', deleted: true } as TrainingData;
when(mockTrainingDataQuery.docs).thenReturn([
{ data: initialFile1 } as TrainingDataDoc,
{ data: initialFile2 } as TrainingDataDoc
{ data: initialFile2 } as TrainingDataDoc,
{ data: deletedFile } as TrainingDataDoc
]);
trainingDataQueryLocalChanges$.next();
tick();
Expand All @@ -438,10 +450,12 @@ describe('DraftSourcesComponent', () => {

// Another client updates the query
const remoteFile = { dataId: 'remote_file' } as TrainingData;
const remoteDeletedFile: TrainingData = { dataId: 'remote_deleted', deleted: true } as TrainingData;
when(mockTrainingDataQuery.docs).thenReturn([
{ data: initialFile1 } as TrainingDataDoc,
{ data: initialFile2 } as TrainingDataDoc,
{ data: remoteFile } as TrainingDataDoc
{ data: remoteFile } as TrainingDataDoc,
{ data: remoteDeletedFile } as TrainingDataDoc
]);
trainingDataQueryLocalChanges$.next();
tick();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ export class DraftSourcesComponent extends DataLoadingComponent implements OnIni
this.savedTrainingFiles?.filter(saved => !this.availableTrainingFiles.includes(saved)) ?? [];
const addedFiles = this.availableTrainingFiles.filter(selected => !this.savedTrainingFiles?.includes(selected));

this.savedTrainingFiles = this.trainingDataQuery?.docs.filter(d => d.data != null).map(d => d.data!) ?? [];
this.savedTrainingFiles =
this.trainingDataQuery?.docs.filter(d => d.data != null && d.data.deleted !== true).map(d => d.data!) ?? [];
this.availableTrainingFiles = this.savedTrainingFiles
.filter(d => removedFiles.findIndex(f => f.dataId === d.dataId) === -1)
.concat(addedFiles);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,36 @@ describe('TrainingDataMultiSelectComponent', () => {

beforeEach(fakeAsync(() => {
mockTrainingData = [
{ dataId: 'data01', fileUrl: '', mimeType: '', skipRows: 0, title: '', projectRef: '', ownerRef: 'user01' },
{ dataId: 'data02', fileUrl: '', mimeType: '', skipRows: 0, title: '', projectRef: '', ownerRef: 'user01' },
{ dataId: 'data03', fileUrl: '', mimeType: '', skipRows: 0, title: '', projectRef: '', ownerRef: 'user01' }
{
dataId: 'data01',
fileUrl: '',
mimeType: '',
skipRows: 0,
title: '',
projectRef: '',
ownerRef: 'user01',
deleted: false
},
{
dataId: 'data02',
fileUrl: '',
mimeType: '',
skipRows: 0,
title: '',
projectRef: '',
ownerRef: 'user01',
deleted: false
},
{
dataId: 'data03',
fileUrl: '',
mimeType: '',
skipRows: 0,
title: '',
projectRef: '',
ownerRef: 'user01',
deleted: false
}
];
when(mockActivatedProjectService.projectId).thenReturn(mockProjectId);
when(mockActivatedProjectService.projectId$).thenReturn(mockProjectId$);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,8 @@ export class TrainingDataUploadDialogComponent implements AfterViewInit {
fileUrl,
mimeType: 'text/csv',
skipRows: (this.skipFirstRow?.checked ?? false) ? 1 : 0,
title: this.trainingDataFile!.fileName!
title: this.trainingDataFile!.fileName!,
deleted: false
};

this.dialogRef.close(trainingData);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { fakeAsync, TestBed, tick } from '@angular/core/testing';
import { getTrainingDataId, TrainingData } from 'realtime-server/lib/esm/scriptureforge/models/training-data';
import { anything, mock, verify } from 'ts-mockito';
import { FileService } from 'xforge-common/file.service';
import { FileType } from 'xforge-common/models/file-offline-data';
import { anything, deepEqual, mock, verify, when } from 'ts-mockito';
import { CommandService } from 'xforge-common/command.service';
import { RealtimeQuery } from 'xforge-common/models/realtime-query';
import { Snapshot } from 'xforge-common/models/snapshot';
import { noopDestroyRef } from 'xforge-common/realtime.service';
import { provideTestRealtime } from 'xforge-common/test-realtime-providers';
import { TestRealtimeService } from 'xforge-common/test-realtime.service';
import { configureTestingModule } from 'xforge-common/test-utils';
import { TypeRegistry } from 'xforge-common/type-registry';
import { PROJECTS_URL } from 'xforge-common/url-constants';
import { TrainingDataDoc } from '../../../core/models/training-data-doc';
import { TrainingDataService } from './training-data.service';

Expand All @@ -24,7 +24,8 @@ describe('TrainingDataService', () => {
mimeType: 'text/csv',
skipRows: 0,
title: 'test.csv',
ownerRef: 'user01'
ownerRef: 'user01',
deleted: false
}
},
{
Expand All @@ -36,25 +37,28 @@ describe('TrainingDataService', () => {
mimeType: 'text/csv',
skipRows: 0,
title: 'test2.csv',
ownerRef: 'user01'
ownerRef: 'user01',
deleted: false
}
}
];
let trainingDataService: TrainingDataService;
let realtimeService: TestRealtimeService;
const mockedFileService = mock(FileService);
const mockedCommandService = mock(CommandService);

configureTestingModule(() => ({
providers: [
provideTestRealtime(new TypeRegistry([TrainingDataDoc], [FileType.TrainingData], [])),
{ provide: FileService, useMock: mockedFileService }
provideTestRealtime(new TypeRegistry([TrainingDataDoc], [], [])),
{ provide: CommandService, useMock: mockedCommandService }
]
}));

beforeEach(() => {
realtimeService = TestBed.inject(TestRealtimeService);
realtimeService.addSnapshots<TrainingData>(TrainingDataDoc.COLLECTION, trainingData);
trainingDataService = TestBed.inject(TrainingDataService);

when(mockedCommandService.onlineInvoke<void>(anything(), anything(), anything())).thenResolve();
});

it('should create a training data doc', fakeAsync(async () => {
Expand All @@ -65,7 +69,8 @@ describe('TrainingDataService', () => {
mimeType: 'text/csv',
skipRows: 0,
title: 'test3.csv',
ownerRef: 'user01'
ownerRef: 'user01',
deleted: false
};
await trainingDataService.createTrainingDataAsync(newTrainingData);
tick();
Expand All @@ -77,40 +82,26 @@ describe('TrainingDataService', () => {
expect(trainingDataDoc.data).toEqual(newTrainingData);
}));

it('should delete a training data doc', fakeAsync(async () => {
// Verify the document exists
const existingTrainingDataDoc = realtimeService.get<TrainingDataDoc>(
TrainingDataDoc.COLLECTION,
getTrainingDataId('project01', 'data01')
);
expect(existingTrainingDataDoc.data?.dataId).toBe('data01');
expect(existingTrainingDataDoc.data?.projectRef).toBe('project01');

// SUT
it('should request deletion via RPC', fakeAsync(async () => {
const trainingDataToDelete: TrainingData = {
projectRef: 'project01',
dataId: 'data01',
fileUrl: '',
mimeType: '',
skipRows: 0,
title: '',
ownerRef: 'user01'
ownerRef: 'user01',
deleted: false
};

await trainingDataService.deleteTrainingDataAsync(trainingDataToDelete);
tick();

const trainingDataDoc = realtimeService.get<TrainingDataDoc>(
TrainingDataDoc.COLLECTION,
getTrainingDataId('project01', 'data01')
);
expect(trainingDataDoc.data).toBeUndefined();
verify(
mockedFileService.deleteFile(
FileType.TrainingData,
'project01',
TrainingDataDoc.COLLECTION,
anything(),
anything()
mockedCommandService.onlineInvoke<void>(
PROJECTS_URL,
'markTrainingDataDeleted',
deepEqual({ projectId: 'project01', dataId: 'data01' })
)
).once();
}));
Expand Down
Loading
Loading