Skip to content
This repository has been archived by the owner on Mar 25, 2023. It is now read-only.

Commit

Permalink
feat(vm-creation): show 'no templates available' when there is no tem…
Browse files Browse the repository at this point in the history
…plates or iso in the vm creation (#524)

Fixes #522
  • Loading branch information
andrewbents committed Sep 15, 2017
1 parent 19a4195 commit 4a54277
Show file tree
Hide file tree
Showing 9 changed files with 241 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/app/vm/vm-creation/data/vm-creation-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export class VmCreationState {
};

public get diskOfferingsAreAllowed(): boolean {
return !this.template.isTemplate;
return !!this.template && !this.template.isTemplate;
}

public getStateFromData(data: VmCreationData): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,15 +87,17 @@ export class VmCreationFormNormalizationService {
}

private filterTemplates(formState: VmCreationFormState): VmCreationFormState {
const filteredTemplates = formState.data.templates.filter(template => {
const templateFits = template.sizeInGB < formState.data.rootDiskSizeLimit;
const templateInZone = template.zoneId === formState.state.zone.id;
const { data, state } = formState;

const filteredTemplates = data.templates.filter(template => {
const templateFits = template.sizeInGB < data.rootDiskSizeLimit;
const templateInZone = template.zoneId === state.zone.id;
return template.isReady && templateFits && templateInZone;
});

const filteredIsos = formState.data.isos.filter(iso => {
const isoFits = iso.sizeInGB < formState.data.rootDiskSizeLimit;
const isoInZone = iso.zoneId === formState.state.zone.id;
const filteredIsos = data.isos.filter(iso => {
const isoFits = iso.sizeInGB < data.rootDiskSizeLimit;
const isoInZone = iso.zoneId === state.zone.id;
return iso.isReady && isoFits && isoInZone && iso.bootable;
});

Expand All @@ -104,11 +106,11 @@ export class VmCreationFormNormalizationService {

const templateStillAvailable = !!formState
.data.installationSources.find(template => {
return formState.state.template.id === template.id;
return state.template && state.template.id === template.id;
});

if (!templateStillAvailable) {
formState.state.template = formState.data.defaultTemplate;
formState.state.template = data.defaultTemplate;
}
return this.getStateFromTemplate(formState);
}
Expand Down Expand Up @@ -148,19 +150,20 @@ export class VmCreationFormNormalizationService {
}

private filterDiskSize(formState: VmCreationFormState): VmCreationFormState {
if (!formState.state.showRootDiskResize) {
const { state } = formState;
if (!state.showRootDiskResize || !state.template) {
return formState;
}

const defaultDiskSize = 1;
const minSize = Math.ceil(Utils.convertToGB(formState.state.template.size)) || defaultDiskSize;
const minSize = Math.ceil(Utils.convertToGB(state.template.size)) || defaultDiskSize;
// e.g. 20000000000 B converts to 20 GB; 200000000 B -> 0.2 GB -> 1 GB; 0 B -> 1 GB
formState.state.rootDiskSizeMin = minSize;
if (
formState.state.rootDiskSize == null ||
formState.state.rootDiskSize < formState.state.rootDiskSizeMin
state.rootDiskSize == null ||
state.rootDiskSize < state.rootDiskSizeMin
) {
formState.state.rootDiskSize = formState.state.rootDiskSizeMin;
formState.state.rootDiskSize = state.rootDiskSizeMin;
}

return formState;
Expand Down
10 changes: 9 additions & 1 deletion src/app/vm/vm-creation/template/vm-template.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
</button>
<div class="mdl-textfield template-list">
<div class="mdl-textfield__input">
{{ 'VM_PAGE.VM_CREATION.OS_TEMPLATE' | translate }}: {{ template?.name }}
<ng-container *ngIf="template; else noTemplates">
{{ 'VM_PAGE.VM_CREATION.OS_TEMPLATE' | translate }}: {{ template?.name }}
</ng-container>
<ng-template #noTemplates>
<!-- todo replace when material2 is merged -->
<span class="mdl-color-text--red">
{{ 'VM_PAGE.VM_CREATION.NO_TEMPLATES' | translate }}
</span>
</ng-template>
</div>
</div>
183 changes: 183 additions & 0 deletions src/app/vm/vm-creation/template/vm-template.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { Component, ViewChild } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { MdDialog } from '@angular/material';
import { By } from '@angular/platform-browser';
import { Observable } from 'rxjs/Observable';
import { MockTranslatePipe } from '../../../../testutils/mocks/mock-translate.pipe.spec';
import { BaseTemplateModel } from '../../../template/shared/base-template.model';
import { Iso } from '../../../template/shared/iso.model';
import { Template } from '../../../template/shared/template.model';
import { VmTemplateDialogComponent } from './vm-template-dialog.component';
import { VmTemplateComponent } from './vm-template.component';

const templatesRaw = require('../../../../testutils/mocks/model-services/fixtures/templates.json');
const isosRaw = require('../../../../testutils/mocks/model-services/fixtures/isos.json');

const templates: Array<Template> = templatesRaw.map(t => new Template(t));
const isos: Array<Iso> = isosRaw.map(i => new Iso(i));

@Component({
selector: 'cs-test',
template: `
<cs-vm-creation-template
[templates]="templates"
[isos]="isos"
[(ngModel)]="template"
></cs-vm-creation-template>
`
})
class TestComponent {
@ViewChild(VmTemplateComponent) public vmTemplateComponent: VmTemplateComponent;
public template: BaseTemplateModel;
public templates: Array<Template>;
public isos: Array<Iso>;
}

describe('VmTemplateComponent', () => {
let component: VmTemplateComponent;
let fixture: ComponentFixture<VmTemplateComponent>;
let template: BaseTemplateModel;

function createTestComponent() {
const f = TestBed.createComponent(TestComponent);
const testComponent = f.componentInstance;
testComponent.templates = templates;
testComponent.isos = isos;
testComponent.template = templates[0];

return { f, testComponent };
}

const mockDialog = {
open: jasmine.createSpy('open').and.callFake(() => {
return {
afterClosed: () => Observable.of(template)
}
})
};

beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [FormsModule],
declarations: [
VmTemplateComponent,
TestComponent,
MockTranslatePipe
],
providers: [
{
provide: MdDialog, useValue: mockDialog
}
]
})
.compileComponents();
}));

it('should support ngModel', async () => {
const { f, testComponent } = createTestComponent();
f.detectChanges();

await f.whenStable();
f.detectChanges();

expect(testComponent.vmTemplateComponent.template).toEqual(templates[0]);
});

it('should reset model if template and iso list are empty', async () => {
const { f, testComponent } = createTestComponent();
f.detectChanges();

await f.whenStable();
f.detectChanges();
expect(testComponent.vmTemplateComponent.template).toEqual(templates[0]);

testComponent.templates = [];
testComponent.isos = [];

await f.whenStable();
f.detectChanges();
expect(testComponent.vmTemplateComponent.template).toBe(null);
});

it('should display selectedTemplate name', async () => {
const { f } = createTestComponent();
f.detectChanges();

await f.whenStable();
f.detectChanges();

const messageContainer = f.debugElement.query(By.css('.mdl-textfield__input'));
expect(messageContainer.nativeElement.textContent.trim()).toBe(
`VM_PAGE.VM_CREATION.OS_TEMPLATE: ${templates[0].name}`
);
});

it('should display error message when templates and isos are empty', async () => {
const { f } = createTestComponent();

await f.whenStable();
f.detectChanges();

const messageContainer = f.debugElement.query(By.css('.mdl-textfield__input'));
expect(messageContainer.nativeElement.textContent.trim()).toBe(
`VM_PAGE.VM_CREATION.NO_TEMPLATES`
);
});

it('should open the dialog', () => {
fixture = TestBed.createComponent(VmTemplateComponent);
component = fixture.componentInstance;
fixture.detectChanges();

component.zoneId = 'someId';
component.template = templates[0];
component.templates = templates;
component.isos = isos;
const button = fixture.debugElement.query(By.css('button'));

button.nativeElement.click();
fixture.detectChanges();

expect(mockDialog.open).toHaveBeenCalled();
const args = mockDialog.open.calls.mostRecent().args;
expect(args[0]).toBe(VmTemplateDialogComponent);
expect(args[1]).toEqual({
width: '780px',
data: {
template: templates[0],
templates: templates,
isos: isos,
zoneId: 'someId'
}
});
});

it('should emit changes after dialog is closed', () => {
fixture = TestBed.createComponent(VmTemplateComponent);
component = fixture.componentInstance;
fixture.detectChanges();

spyOn(component.change, 'next');
component.zoneId = 'someId';
component.templates = templates;
component.isos = isos;
const button = fixture.debugElement.query(By.css('button'));

button.nativeElement.click();
fixture.detectChanges();

expect(component.change.next).toHaveBeenCalledTimes(0);
expect(component.template).toBeUndefined();

template = templates[0];
button.nativeElement.click();
fixture.detectChanges();
fixture.detectChanges();
fixture.detectChanges();

expect(component.change.next).toHaveBeenCalledTimes(1);
expect(component.change.next).toHaveBeenCalledWith(templates[0]);
expect(component.template).toEqual(templates[0]);
});
});
23 changes: 19 additions & 4 deletions src/app/vm/vm-creation/template/vm-template.component.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Component, EventEmitter, forwardRef, Input, Output } from '@angular/core';
import {
Component,
EventEmitter,
forwardRef,
Input,
OnChanges,
Output,
SimpleChanges
} from '@angular/core';
import { NG_VALUE_ACCESSOR } from '@angular/forms';
import { MdDialog } from '@angular/material';
import { Observable } from 'rxjs/Observable';
import { BaseTemplateModel, Iso, Template } from '../../../template/shared';
import { VmTemplateDialogComponent } from './vm-template-dialog.component';


@Component({
selector: 'cs-vm-creation-template',
templateUrl: 'vm-template.component.html',
Expand All @@ -17,18 +24,26 @@ import { VmTemplateDialogComponent } from './vm-template-dialog.component';
}
]
})
export class VmTemplateComponent {
export class VmTemplateComponent implements OnChanges {
@Input() public templates: Array<Template>;
@Input() public isos: Array<Iso>;
@Input() public zoneId: string;
@Output() public change: EventEmitter<BaseTemplateModel>;

private _template: BaseTemplateModel;
private _template: BaseTemplateModel | null;

constructor(private dialog: MdDialog) {
this.change = new EventEmitter();
}

public ngOnChanges(changes: SimpleChanges): void {
if (changes.templates || changes.isos) {
if (!this.templates.length && !this.isos.length) {
this.template = null;
}
}
}

public onClick(): void {
this.showTemplateSelectionDialog()
.subscribe(template => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/vm/vm-creation/vm-creation.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ <h5>{{ 'VM_PAGE.VM_CREATION.SSH_KEY_PAIR' | translate }}</h5>
<div class="mat-dialog-actions">
<button
mdl-colored="primary"
[disabled]="!vmCreateForm.valid || formState?.state.displayName === takenName"
[disabled]="!vmCreateForm.valid || nameIsTaken || !formState?.state.template"
type="submit"
mdl-button
mdl-ripple
Expand Down
11 changes: 8 additions & 3 deletions src/app/vm/vm-creation/vm-creation.component.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { MdSelectChange, MdDialogRef } from '@angular/material';
import { MdDialogRef, MdSelectChange } from '@angular/material';
import * as throttle from 'lodash/throttle';

import { DialogService } from '../../dialog/dialog-service/dialog.service';
Expand Down Expand Up @@ -98,10 +98,15 @@ export class VmCreationComponent implements OnInit {
});
}

public get nameIsTaken(): boolean {
return !!this.formState && this.formState.state.displayName === this.takenName;
}

public get showResizeSlider(): boolean {
return (
this.formState.state.template.isTemplate ||
this.formState.state.showRootDiskResize
!!this.formState.state.template &&
(this.formState.state.template.isTemplate ||
this.formState.state.showRootDiskResize)
);
}

Expand Down
3 changes: 2 additions & 1 deletion src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,8 @@
"CREATING_AG": "Creating an affinity group",
"CREATING_SG": "Creating a security group",
"DEPLOYING_VM": "Deploying the virtual machine",
"OS_TEMPLATE": "OS template"
"OS_TEMPLATE": "OS template",
"NO_TEMPLATES": "No templates available"
}
},
"SPARE_DRIVE_PAGE": {
Expand Down
3 changes: 2 additions & 1 deletion src/i18n/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -447,7 +447,8 @@
"CREATING_SG": "Создание группы безопасности",
"CREATING_AG": "Создание аффинитетной группы",
"DEPLOYING_VM": "Создание виртуальной машины",
"OS_TEMPLATE": "ОС шаблон"
"OS_TEMPLATE": "ОС шаблон",
"NO_TEMPLATES": "Нет доступных шаблонов"
}
},
"SPARE_DRIVE_PAGE": {
Expand Down

0 comments on commit 4a54277

Please sign in to comment.