Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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 @@ -24,6 +24,7 @@ export class CloudFilePickerDialogComponent {
showTreeView: false,
selectable: FileExplorerSelectable.file,
};

private _saved = false;

public set containerId(containerId: string) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
import { Component, Input, OnDestroy, OnInit, forwardRef } from "@angular/core";
import { Component, Input, OnChanges, OnDestroy, forwardRef } from "@angular/core";
import {
ControlValueAccessor, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR,
} from "@angular/forms";
import { autobind } from "app/core";
import { List } from "immutable";
import { Subscription } from "rxjs";

import { DialogService } from "app/components/base/dialogs";
import { BlobContainer } from "app/models";
import { ListContainerParams, StorageService } from "app/services";
import { ListView } from "app/services/core";
import { Constants } from "common";
import { StorageService } from "app/services";
import { CloudFilePickerDialogComponent } from "./cloud-file-picker-dialog.component";
import "./cloud-file-picker.scss";

Expand All @@ -23,7 +19,7 @@ import "./cloud-file-picker.scss";
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => CloudFilePickerComponent), multi: true },
],
})
export class CloudFilePickerComponent implements ControlValueAccessor, OnInit, OnDestroy {
export class CloudFilePickerComponent implements ControlValueAccessor, OnChanges, OnDestroy {
@Input() public label: string;
@Input() public hint: string;

Expand All @@ -32,20 +28,13 @@ export class CloudFilePickerComponent implements ControlValueAccessor, OnInit, O
*/
@Input() public containerId: string;

public fileGroups: List<BlobContainer>;
public value = new FormControl();
public fileGroupsData: ListView<BlobContainer, ListContainerParams>;
public warning = false;

private _propagateChange: (value: any[]) => void = null;
private _subscriptions: Subscription[] = [];

constructor(private storageService: StorageService, private dialog: DialogService) {
this.fileGroupsData = this.storageService.containerListView(Constants.ncjFileGroupPrefix);
this.fileGroupsData.items.subscribe((fileGroups) => {
this.fileGroups = fileGroups;
});

this._subscriptions.push(this.value.valueChanges.debounceTime(400).distinctUntilChanged().subscribe((value) => {
this._checkValid(value);
if (this._propagateChange) {
Expand All @@ -54,13 +43,15 @@ export class CloudFilePickerComponent implements ControlValueAccessor, OnInit, O
}));
}

public ngOnInit() {
this.fileGroupsData.fetchNext();
public ngOnChanges(changes) {
if (changes.containerId) {
// check validity if the selected file group changes
this._checkValid(this.value.value);
}
}

public ngOnDestroy() {
this._subscriptions.forEach(x => x.unsubscribe());
this.fileGroupsData.dispose();
}

public writeValue(value: string) {
Expand Down Expand Up @@ -90,11 +81,22 @@ export class CloudFilePickerComponent implements ControlValueAccessor, OnInit, O
this.value.setValue(component.pickedFile);
}
});

return component.done;
}

private _checkValid(value: string) {
const valid = !value || this.fileGroups.map(x => x.name).includes(value);
this.warning = !valid;
if (!value) {
this.warning = false;
return;
}

// validate that the blob exists in the selected container
// note: value includes prefix
this.storageService.getBlobPropertiesOnce(this.containerId, value).subscribe((blob) => {
this.warning = false;
}, (error) => {
this.warning = true;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<mat-form-field>
<input type="text" matInput [formControl]="value" [placeholder]="label">
<mat-hint class="warning" *ngIf="warning">
The file does not seem to exist in the selected file group. Click the 'Select file' button and find the file location manually.
</mat-hint>
<mat-hint *ngIf="hint" align="end">
{{hint}}
</mat-hint>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ bl-cloud-file-picker {

mat-form-field {
flex: 1;

.warning {
color: map-get($warn, 500);
}
}

bl-button {
Expand Down
213 changes: 213 additions & 0 deletions app/components/market/submit/submit-ncj-template.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
import { Component, DebugElement, NO_ERRORS_SCHEMA } from "@angular/core";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FormBuilder, FormsModule, ReactiveFormsModule } from "@angular/forms";
import { By } from "@angular/platform-browser";
import { NoopAnimationsModule } from "@angular/platform-browser/animations";
import { ActivatedRoute, Router } from "@angular/router";
import { RouterTestingModule } from "@angular/router/testing";
import { List } from "immutable";
import { BehaviorSubject, Subject } from "rxjs";
import { Observable } from "rxjs/Observable";

import { DialogService } from "app/components/base/dialogs";
import { SidebarManager } from "app/components/base/sidebar";
import { FileGroupPickerComponent } from "app/components/data/shared";
import { CloudFilePickerComponent } from "app/components/data/shared/cloud-file-picker";
import { FileGroupSasComponent } from "app/components/data/shared/file-group-sas";
import { PoolPickerComponent } from "app/components/job/action/add";
import { ParameterInputComponent, SubmitNcjTemplateComponent } from "app/components/market/submit";
import { MaterialModule } from "app/core";
import { NcjJobTemplate, NcjParameterRawType, NcjPoolTemplate, NcjTemplateMode, Pool } from "app/models";
import { NcjSubmitService, NcjTemplateService, PoolService, StorageService, VmSizeService } from "app/services";
import * as Fixtures from "test/fixture";
import { MockListView } from "test/utils/mocks";
import { NoItemMockComponent } from "test/utils/mocks/components";

@Component({
template: `
<bl-submit-ncj-template
title="test title"
[jobTemplate]="jobTemplate"
[poolTemplate]="poolTemplate">
</bl-submit-ncj-template>
`,
})
class TestComponent {
public jobTemplate: NcjJobTemplate = {
parameters: {
poolId: {
type: NcjParameterRawType.string,
defaultValue: "test-pool",
metadata: {},
},
inputData: {
type: NcjParameterRawType.string,
metadata: {
advancedType: "file-group",
},
},
blendFile: {
type: NcjParameterRawType.string,
metadata: {
advancedType: "file-in-file-group",
dependsOn: "inputData",
},
},
},
job: {
type: "Microsoft.Batch/batchAccounts/jobs",
properties: {
poolId: "[parameters('poolId')]",
fileGroup: "[parameters('inputData')]",
blendFile: "[parameters('blendFile')]",
},
},
};

public poolTemplate: NcjPoolTemplate = {
parameters: {
poolId: {
type: NcjParameterRawType.string,
metadata: {
description: "test pool data",
},
},
},
variables: null,
pool: {
id: "[parameters('poolId')]",
},
};
}

describe("SubmitNcjTemplateComponent", () => {
let fixture: ComponentFixture<TestComponent>;
let testComponent: TestComponent;
let component: SubmitNcjTemplateComponent;
let de: DebugElement;

let activatedRouteSpy: any;
let templateServiceSpy: any;
let ncjSubmitServiceSpy: any;
let routerSpy: any;
let storageServiceSpy: any;
let poolServiceSpy: any;
let vmSizeServiceSpy: any;
let sidebarSpy: any;
let dialogSpy: any;
let listProxy: MockListView<any, any>;

const blendFile = "myscene.blend";
const queryParameters = {
useAutoPool: "0",
blendFile: blendFile,
};

beforeEach(() => {
listProxy = new MockListView(Pool, {
cacheKey: "id",
items: [
Fixtures.pool.create({ id: "pool-1" }),
],
});

activatedRouteSpy = {
queryParams: new BehaviorSubject(queryParameters),
};

poolServiceSpy = {
listView: () => listProxy,
};

vmSizeServiceSpy = {
sizes: Observable.of(List([])),
};

routerSpy = {
navigate: jasmine.createSpy("router.navigate"),
navigateByUrl: jasmine.createSpy("router.navigateByUrl"),
createUrlTree: jasmine.createSpy("router.createUrlTree"),
};

templateServiceSpy = {
addRecentSubmission: jasmine.createSpy("addRecentSubmission"),
};

ncjSubmitServiceSpy = {
expandPoolTemplate: jasmine.createSpy("expandPoolTemplate").and.callFake((poolTemplate, poolParams) => {
return Observable.of({});
}),
submitJob: jasmine.createSpy("submitJob").and.callFake((jobTemplate, jobParams) => {
return Observable.of({});
}),
createPool: jasmine.createSpy("createPool").and.callFake((poolTemplate, poolParams) => {
return Observable.of({});
}),
};

storageServiceSpy = {
onContainerAdded: new Subject(),
containerListView: () => listProxy,
};

dialogSpy = {
open: jasmine.createSpy("open"),
};

sidebarSpy = {
open: jasmine.createSpy("open"),
};

TestBed.configureTestingModule({
imports: [RouterTestingModule, ReactiveFormsModule, FormsModule, MaterialModule, NoopAnimationsModule],
declarations: [NoItemMockComponent, SubmitNcjTemplateComponent, FileGroupSasComponent,
TestComponent, FileGroupPickerComponent, CloudFilePickerComponent, ParameterInputComponent,
PoolPickerComponent],
providers: [
{ provide: FormBuilder, useValue: new FormBuilder() },
{ provide: ActivatedRoute, useValue: activatedRouteSpy },
{ provide: Router, useValue: routerSpy },
{ provide: NcjTemplateService, useValue: templateServiceSpy },
{ provide: NcjSubmitService, useValue: ncjSubmitServiceSpy },
{ provide: StorageService, useValue: storageServiceSpy },
{ provide: DialogService, useValue: dialogSpy },
{ provide: SidebarManager, useValue: sidebarSpy },
{ provide: PoolService, useValue: poolServiceSpy },
{ provide: VmSizeService, useValue: vmSizeServiceSpy },
],

schemas: [NO_ERRORS_SCHEMA],
});

fixture = TestBed.createComponent(TestComponent);
testComponent = fixture.componentInstance;
de = fixture.debugElement.query(By.css("bl-submit-ncj-template"));
component = de.componentInstance;
fixture.detectChanges();
});

it("poolId should have been removed from the job template", () => {
expect(testComponent.jobTemplate.parameters.poolId).toBe(undefined);
});

it("should set mode to pool picker mode due to initial query params", () => {
expect(component.modeState).toBe(NcjTemplateMode.ExistingPoolAndJob);
});

it("should set blend file from query parameter", () => {
expect(component.form.value.job.blendFile).toBe(blendFile);
expect(component.jobParams.value.blendFile).toBe(blendFile);
});

describe("Change query parameter to not use autopool", () => {
beforeEach(() => {
queryParameters.useAutoPool = "1";
activatedRouteSpy.queryParams.next(queryParameters);
fixture.detectChanges();
});

it("should set mode to create auto-pool", () => {
expect(component.modeState).toBe(NcjTemplateMode.NewPoolAndJob);
});
});
});
Loading