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
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ class TestEnvironment {
['1', '0', 'v2']
]);
when(
mockedFileService.onlineUploadFileOrFail(
mockedFileService.tryOnlineUploadFile(
FileType.Audio,
anything(),
TextAudioDoc.COLLECTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ export class ChapterAudioDialogComponent implements AfterViewInit, OnDestroy {
if (!this.onlineStatusService.isOnline) return;

this._loadingAudio = true;
const audioUrl: string | undefined = await this.fileService.onlineUploadFileOrFail(
const audioUrl: string | undefined = await this.fileService.tryOnlineUploadFile(
FileType.Audio,
this.data.projectId,
TextAudioDoc.COLLECTION,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ <h2 class="heading-source-side">{{ t("overview_reference") }} {{ preHyphenate(re
<button
mat-button
(click)="trainingSources.push(undefined); $event.preventDefault()"
[disabled]="!appOnline"
class="add-another-project"
>
<mat-icon>add</mat-icon> {{ t("add_another_reference_project") }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
}
</mat-list>
}
<button mat-button (click)="openUploadDialog()">
<button mat-button (click)="openUploadDialog()" [disabled]="!appOnline">
<mat-icon>file_upload</mat-icon>
{{ t("upload_spreadsheet") }}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { FileService } from 'xforge-common/file.service';
import { I18nService } from 'xforge-common/i18n.service';
import { FileType } from 'xforge-common/models/file-offline-data';
import { Locale } from 'xforge-common/models/i18n-locale';
import { OnlineStatusService } from 'xforge-common/online-status.service';
import { configureTestingModule, getTestTranslocoModule } from 'xforge-common/test-utils';
import { UserService } from 'xforge-common/user.service';
import { SFProjectProfileDoc } from '../../../core/models/sf-project-profile-doc';
Expand All @@ -22,6 +23,7 @@ const mockActivatedProjectService = mock(ActivatedProjectService);
const mockDialogService = mock(DialogService);
const mockFileService = mock(FileService);
const mockI18nService = mock(I18nService);
const mockOnlineStatusService = mock(OnlineStatusService);
const mockTrainingDataService = mock(TrainingDataService);
const mockTrainingDataUploadDialogComponent = mock<MatDialogRef<TrainingDataUploadDialogComponent>>(MatDialogRef);
const mockUserService = mock(UserService);
Expand Down Expand Up @@ -56,6 +58,7 @@ describe('TrainingDataMultiSelectComponent', () => {
{ provide: DialogService, useMock: mockDialogService },
{ provide: FileService, useMock: mockFileService },
{ provide: I18nService, useMock: mockI18nService },
{ provide: OnlineStatusService, useMock: mockOnlineStatusService },
{ provide: TrainingDataService, useMock: mockTrainingDataService },
{ provide: UserService, useMock: mockUserService }
]
Expand Down Expand Up @@ -105,6 +108,7 @@ describe('TrainingDataMultiSelectComponent', () => {
mockDialogService.confirm('training_data_multi_select.confirm_delete', 'training_data_multi_select.delete')
).thenResolve(true);
when(mockI18nService.locale$).thenReturn(of(locale));
when(mockOnlineStatusService.isOnline).thenReturn(true);
when(mockTrainingDataUploadDialogComponent.afterClosed()).thenReturn(of({ dataId: 'data04' }));
fixture = TestBed.createComponent(TrainingDataMultiSelectComponent);
component = fixture.componentInstance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ActivatedProjectService } from 'xforge-common/activated-project.service
import { DialogService } from 'xforge-common/dialog.service';
import { FileService } from 'xforge-common/file.service';
import { FileType } from 'xforge-common/models/file-offline-data';
import { OnlineStatusService } from 'xforge-common/online-status.service';
import { UserService } from 'xforge-common/user.service';
import {
TrainingDataUploadDialogComponent,
Expand All @@ -33,9 +34,14 @@ export class TrainingDataMultiSelectComponent {
private readonly activatedProjectService: ActivatedProjectService,
private readonly dialogService: DialogService,
private readonly fileService: FileService,
private readonly onlineStatus: OnlineStatusService,
private readonly userService: UserService
) {}

get appOnline(): boolean {
return this.onlineStatus.isOnline;
}

canDeleteTrainingData(trainingData: TrainingData): boolean {
const userId: string = this.userService.currentUserId;
const project: SFProjectProfile | undefined = this.activatedProjectService.projectDoc?.data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
import { OverlayContainer } from '@angular/cdk/overlay';
import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
import { NgZone } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { ngfModule } from 'angular-file';
import { TrainingData } from 'realtime-server/lib/esm/scriptureforge/models/training-data';
import { anything, mock, when } from 'ts-mockito';
import { anything, mock, verify, when } from 'ts-mockito';
import { DialogService } from 'xforge-common/dialog.service';
import { FileService } from 'xforge-common/file.service';
import { FileType } from 'xforge-common/models/file-offline-data';
import { provideTestOnlineStatus } from 'xforge-common/test-online-status-providers';
import { ChildViewContainerComponent, configureTestingModule, getTestTranslocoModule } from 'xforge-common/test-utils';
import { UserService } from 'xforge-common/user.service';
import { TrainingDataDoc } from '../../../core/models/training-data-doc';
import { TrainingDataFileUpload, TrainingDataUploadDialogComponent } from './training-data-upload-dialog.component';
import { TrainingDataService } from './training-data.service';

const mockedDialogService = mock(DialogService);
const mockedFileService = mock(FileService);
const mockedTrainingDataService = mock(TrainingDataService);
const mockedUserService = mock(UserService);
Expand All @@ -23,8 +25,8 @@ describe('TrainingDataUploadDialogComponent', () => {
configureTestingModule(() => ({
imports: [ngfModule, getTestTranslocoModule()],
providers: [
provideHttpClient(withInterceptorsFromDi()),
provideHttpClientTesting(),
provideTestOnlineStatus(),
{ provide: DialogService, useMock: mockedDialogService },
{ provide: FileService, useMock: mockedFileService },
{ provide: TrainingDataService, useMock: mockedTrainingDataService },
{ provide: UserService, useMock: mockedUserService }
Expand All @@ -40,6 +42,113 @@ describe('TrainingDataUploadDialogComponent', () => {
overlayContainer.ngOnDestroy();
});

it('cannot save if offline', async () => {
const env = new TestEnvironment();
when(
mockedFileService.onlineUploadFile(
anything(),
anything(),
anything(),
anything(),
anything(),
anything(),
anything()
)
).thenThrow(new HttpErrorResponse({ status: 0 }));
let result: TrainingData = { dataId: '' } as TrainingData;
env.dialogRef.afterClosed().subscribe((_result: TrainingData) => {
result = _result;
});
env.component.updateTrainingData(env.trainingDataFile);
await env.component.save();
await env.wait();

verify(
mockedFileService.onlineUploadFile(
anything(),
anything(),
anything(),
anything(),
anything(),
anything(),
anything()
)
).once();
expect(result.dataId).toEqual('');
});

it('should show an error message if the file is invalid', async () => {
const env = new TestEnvironment();
when(
mockedFileService.onlineUploadFile(
anything(),
anything(),
anything(),
anything(),
anything(),
anything(),
anything()
)
).thenThrow(new HttpErrorResponse({ status: HttpStatusCode.BadRequest }));
let result: TrainingData = { dataId: '' } as TrainingData;
env.dialogRef.afterClosed().subscribe((_result: TrainingData) => {
result = _result;
});
env.component.updateTrainingData(env.trainingDataFile);
await env.component.save();
await env.wait();

verify(
mockedFileService.onlineUploadFile(
anything(),
anything(),
anything(),
anything(),
anything(),
anything(),
anything()
)
).once();
verify(mockedDialogService.message(anything())).once();
expect(result.dataId).toEqual('');
});

it('should show an error message if the file for all other errors', async () => {
const env = new TestEnvironment();
when(
mockedFileService.onlineUploadFile(
anything(),
anything(),
anything(),
anything(),
anything(),
anything(),
anything()
)
).thenThrow(new HttpErrorResponse({ status: HttpStatusCode.NotFound }));
let result: TrainingData = { dataId: '' } as TrainingData;
env.dialogRef.afterClosed().subscribe((_result: TrainingData) => {
result = _result;
});
env.component.updateTrainingData(env.trainingDataFile);
await env.component.save();
await env.wait();

verify(
mockedFileService.onlineUploadFile(
anything(),
anything(),
anything(),
anything(),
anything(),
anything(),
anything()
)
).once();
verify(mockedDialogService.message(anything())).once();
expect(result.dataId).toEqual('');
});

it('should upload training data and return the object on save', async () => {
const env = new TestEnvironment();
let result: TrainingData = { dataId: '' } as TrainingData;
Expand Down Expand Up @@ -111,7 +220,7 @@ class TestEnvironment {

constructor(availableTrainingData: TrainingData[] = []) {
when(
mockedFileService.onlineUploadFileOrFail(
mockedFileService.onlineUploadFile(
FileType.TrainingData,
anything(),
TrainingDataDoc.COLLECTION,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { NgClass } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import { AfterViewInit, Component, ElementRef, Inject, ViewChild } from '@angular/core';
import { MatButton } from '@angular/material/button';
import { MatCheckbox } from '@angular/material/checkbox';
Expand Down Expand Up @@ -112,26 +113,37 @@ export class TrainingDataUploadDialogComponent implements AfterViewInit {
}

async save(): Promise<void> {
// We cannot save a file if it has not been uploaded, or if offline
// We cannot save a file if it has not been uploaded
if (!this.hasBeenUploaded) {
return;
}

this._isUploading = true;
const dataId: string = objectId();
const fileUrl: string | undefined = await this.fileService.onlineUploadFileOrFail(
FileType.TrainingData,
this.data.projectId,
TrainingDataDoc.COLLECTION,
dataId,
this.trainingDataFile!.blob!,
this.trainingDataFile!.fileName!,
true
);
this._isUploading = false;
if (fileUrl == null) {
void this.dialogService.message('training_data_upload_dialog.upload_failed');
let fileUrl: string | undefined;
try {
fileUrl = await this.fileService.onlineUploadFile(
FileType.TrainingData,
this.data.projectId,
TrainingDataDoc.COLLECTION,
dataId,
this.trainingDataFile!.blob!,
this.trainingDataFile!.fileName!,
true
);
if (fileUrl === undefined) {
void this.dialogService.message('training_data_upload_dialog.no_upload_offline');
return;
}
} catch (e) {
if (e instanceof HttpErrorResponse && e.status === 400) {
void this.dialogService.message('training_data_upload_dialog.invalid_format');
} else {
void this.dialogService.message('training_data_upload_dialog.upload_failed');
}
return;
} finally {
this._isUploading = false;
}

// Create the training_data record
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -884,11 +884,12 @@
"drag_and_drop_files": "Drag and drop training data file here",
"drag_and_drop_or_browse": "OR",
"filename_already_exists": "A file already exists with this name.",
"invalid_format": "Your file needs to have two columns, and every row must have a value in both columns. Please fill in any empty cells and try again.",
"skip_first_row": "Skip first row of data file",
"no_training_data_file_uploaded": "No training data file uploaded",
"no_upload_offline": "Training data cannot be uploaded without connecting to the internet.",
"save": "Save",
"upload_failed": "Your file needs to have two columns, and every row must have a value in both columns. Please fill in any empty cells and try again.",
"upload_failed": "The training data file could not be uploaded. Please try again or use a different file.",
"upload_training_data": "Upload Training Data",
"uploading": "Uploading..."
},
Expand Down
Loading
Loading