Skip to content

Commit

Permalink
Merge pull request #50 from CATechnologiesTest/apply-yaml
Browse files Browse the repository at this point in the history
Apply yaml
  • Loading branch information
bl8nr authored Jan 14, 2019
2 parents 6325751 + 93d8e0a commit d0bf1b0
Show file tree
Hide file tree
Showing 14 changed files with 211 additions and 147 deletions.
3 changes: 2 additions & 1 deletion api/Dockerfile.experimental
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
FROM yipeeio/api
ARG from_image=yipee-api:latest
FROM $from_image
WORKDIR /usr/src/app
# add kubectl to base API image...
ADD https://storage.googleapis.com/kubernetes-release/release/${KUBECTL_VSN:-v1.11.2}/bin/linux/amd64/kubectl /usr/src/app/kubectl
Expand Down
1 change: 1 addition & 0 deletions api/api_build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ docker rm -f ${CVT_NAME}

if [ $TESTRC -eq 0 ]; then
docker build -t $2 .
docker build --build-arg from_image=$2 -t $2-experimental -f Dockerfile.experimental .
else
exit $TESTRC
fi
2 changes: 2 additions & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ npm-debug.log
testem.log
/typings
yarn-error.log
/allure-results


# e2e
/e2e/*.js
Expand Down
7 changes: 7 additions & 0 deletions ui/src/app/editor/editor.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ <h4 class="dropdown-header">Canvas</h4>
</label>
</div>
</div>
<button type="button" class="btn btn-icon" title="Apply" (click)='onApplyManifestClicked()' [disabled]="this.editorService.invalidKeys.length > 0">
<clr-icon *ngIf="!isApplyingManifest" shape="upload-cloud" class="is-solid"></clr-icon>
<span *ngIf="isApplyingManifest" class="spinner spinner-inline">
</span>
</button>

<button type="button" class="btn btn-icon" title="Close" (click)="exitEditor()">
<clr-icon shape="times-circle" class="is-solid"></clr-icon>
</button>
Expand Down Expand Up @@ -137,3 +143,4 @@ <h4 class="dropdown-header">Canvas</h4>
(onClose)="showWarningModal = false; disregardChanges = true; exitEditor();"
(onCancel)="showWarningModal = false;">
</app-warn-changes-modal>

132 changes: 84 additions & 48 deletions ui/src/app/editor/editor.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,13 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/
import { RouterTestingModule } from '@angular/router/testing';
import { Location } from '@angular/common';

import { Observable } from 'rxjs/Observable';
import { of } from 'rxjs';

import { EditorComponent } from './editor.component';

import { EditorService } from './editor.service';
import { YipeeFileService } from '../shared/services/yipee-file.service';
import { YipeeFileMetadata } from '../models/YipeeFileMetadata';
import { YipeeFileMetadataRaw } from '../models/YipeeFileMetadataRaw';
import { YipeeFileResponse, YipeeFileErrorResponse } from '../models/YipeeFileResponse';
import { DownloadService } from '../shared/services/download.service';
import { EditorEventService, SelectionChangedEvent, EventSource } from './editor-event.service';
import { EventEmitter } from '@angular/core';
import { EditorEventService } from './editor-event.service';
import { ActivatedRoute } from '@angular/router';
import { ApiService } from '../shared/services/api.service';
import { UserService } from '../shared/services/user.service';
Expand All @@ -30,10 +24,6 @@ describe('EditorComponent', () => {
class MockDownloadService {
constructor() { }
}
class MockYipeeFileService {
constructor() { }
onSelectionChange: EventEmitter<SelectionChangedEvent> = new EventEmitter();
}
class MockActivatedRoute {
constructor() { }
snapshot = { params: {} };
Expand All @@ -49,32 +39,6 @@ describe('EditorComponent', () => {
constructor() { }
}

class MockEditorService {
yipeeFileID: string;
metadata: YipeeFileMetadata;
fatalText: string[] = [];
alertText: string[] = [];
warningText: string[] = [];
infoText: string[] = [];
invalidKeys: string[];
lastYipeeId: string;
dirty: boolean;

constructor() {
this.invalidKeys = [];
this.metadata = new YipeeFileMetadata();
this.dirty = false;
}
setYipeeFileID(yipeeFileId: string): Observable<boolean> {
this.yipeeFileID = yipeeFileId;
return of(true);
}
loadYipeeFile(id): Observable<boolean> {
this.lastYipeeId = id;
this.metadata = YipeeFileService.newTestYipeeFileMetadata('test');
return of(true);
}
}
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
Expand All @@ -94,8 +58,8 @@ describe('EditorComponent', () => {
YipeeFileService,
ApiService,
{ provide: DownloadService, useClass: MockDownloadService },
{ provide: EditorService, useClass: MockEditorService },
{ provide: ActivatedRoute, useClass: MockActivatedRoute }
{ provide: ActivatedRoute, useClass: MockActivatedRoute },
EditorService
]
})
.compileComponents();
Expand All @@ -113,12 +77,15 @@ describe('EditorComponent', () => {
}));

it('should be created', () => {
// Since we are using the real editor service, we need to initialize the metadata
component.editorService.metadata = new YipeeFileMetadata();

fixture.detectChanges();
expect(component).toBeTruthy();
});

it('should load model from URL', async(inject([EditorService, ApiService, ActivatedRoute, HttpTestingController],
(service: MockEditorService, apiService: ApiService, ar: MockActivatedRoute, backend: HttpTestingController) => {
it('should load model from URL', async(inject([ActivatedRoute, HttpTestingController],
(ar: MockActivatedRoute, backend: HttpTestingController) => {
ar.addId('foo');
expect(component.ui.loading).toBeTruthy();
fixture.detectChanges();
Expand All @@ -135,8 +102,8 @@ describe('EditorComponent', () => {
expect(component.ui.loading).toBeFalsy();
})));

it('should handle invalid model error, where 200 is returned', async(inject([EditorService, ApiService, ActivatedRoute, HttpTestingController],
(service: MockEditorService, apiService: ApiService, ar: MockActivatedRoute, backend: HttpTestingController) => {
it('should handle invalid model error, where 200 is returned', async(inject([EditorService, ActivatedRoute, HttpTestingController],
(service: EditorService, ar: MockActivatedRoute, backend: HttpTestingController) => {
ar.addId(MockApiService.BAD_MODEL);
expect(component.ui.loading).toBeTruthy();
fixture.detectChanges();
Expand All @@ -151,7 +118,7 @@ describe('EditorComponent', () => {
})));

it('should handle invalid model error, where 4xx is returned', async(inject([EditorService, ActivatedRoute, HttpTestingController],
(service: MockEditorService, ar: MockActivatedRoute, backend: HttpTestingController) => {
(service: EditorService, ar: MockActivatedRoute, backend: HttpTestingController) => {
ar.addId('foo');
expect(component.ui.loading).toBeTruthy();
fixture.detectChanges();
Expand All @@ -165,7 +132,7 @@ describe('EditorComponent', () => {
})));

it('should handle a network error', async(inject([EditorService, ActivatedRoute, HttpTestingController, NgZone],
(service: MockEditorService, ar: MockActivatedRoute, backend: HttpTestingController, ngZone: NgZone) => {
(service: EditorService, ar: MockActivatedRoute, backend: HttpTestingController, ngZone: NgZone) => {
ar.addId('foo');
fixture.detectChanges();
const req = ngZone.run(() => backend.expectOne('/api/import/foo') );
Expand All @@ -182,7 +149,7 @@ describe('EditorComponent', () => {
expect(service.fatalText[0].indexOf(EditorService.UNEXPECTED_RESPONSE) >= 0).toBeTruthy();
})));

it('should set dirty flag and route to home when exitEditor is called with disregardChanges set to true', fakeAsync(inject([EditorService, NgZone], (service: MockEditorService, ngZone: NgZone) => {
it('should set dirty flag and route to home when exitEditor is called with disregardChanges set to true', fakeAsync(inject([EditorService, NgZone], (service: EditorService, ngZone: NgZone) => {
expect(component).toBeTruthy();
expect(location.path() === '').toBeTruthy();
service.dirty = true;
Expand All @@ -195,14 +162,14 @@ describe('EditorComponent', () => {
expect(location.path()).toBe('/');
})));

it('should set showWarningModal to true when exitEditor is called with EditorService dirty flag set to true', fakeAsync(inject([EditorService, NgZone], (service: MockEditorService, ngZone: NgZone) => {
it('should set showWarningModal to true when exitEditor is called with EditorService dirty flag set to true', fakeAsync(inject([EditorService, NgZone], (service: EditorService, ngZone: NgZone) => {
expect(component.showWarningModal).toEqual(false);
service.dirty = true;
ngZone.run(() => component.canDeactivate());
expect(component.showWarningModal).toEqual(true);
})));

it('should call router.navigate home when exitEditor is called with EditorService dirty flag set to false', fakeAsync(inject([EditorService, NgZone], (service: MockEditorService, ngZone: NgZone) => {
it('should call router.navigate home when exitEditor is called with EditorService dirty flag set to false', fakeAsync(inject([EditorService, NgZone], (service: EditorService, ngZone: NgZone) => {
expect(location.path() === '').toBeTruthy();
expect(component.showWarningModal).toEqual(false);
expect(service.dirty).toBeFalsy();
Expand All @@ -211,4 +178,73 @@ describe('EditorComponent', () => {
expect(location.path()).toBe('/');
})));

it('should handle a successful apply', fakeAsync(inject([HttpTestingController, EditorService], (backend: HttpTestingController, service: EditorService) => {
const ns = 'foo';
service.metadata = new YipeeFileMetadata();
service.metadata.name = ns;
service.metadata.flatFile.appInfo.namespace = ns;
[true, false].forEach( createNs => {
expect(component.editorService.infoText.length).toBe(0);
service.metadata.flatFile.appInfo.createNs = createNs;
component.onApplyManifestClicked();
backend.expectOne({method: 'POST', url: '/api/namespaces/apply/' + ns + ((createNs) ? '?createNamespace=true' : '' )})
.flush({success: true, total: 1, data: ['applied successfully']});
tick(50);
expect(component.editorService.infoText.length).toBe(1);
component.editorService.infoText.length = 0;
});
})));

it('should handle an error from a namespace apply', fakeAsync(inject([HttpTestingController, EditorService], (backend: HttpTestingController, service: EditorService) => {

const ns = 'foo';
const err = 'bad apply';
service.metadata = new YipeeFileMetadata();
service.metadata.name = ns;
service.metadata.flatFile.appInfo.namespace = ns;
service.metadata.flatFile.appInfo.createNs = true;
component.onApplyManifestClicked();
expect(component.editorService.warningText.length).toBe(0);
backend.expectOne({method: 'POST', url: '/api/namespaces/apply/' + ns + '?createNamespace=true'})
.flush({success: false, total: 0, data: [err]}, {status: 500, statusText: 'badDev'});
tick(50);
expect(component.editorService.warningText.length).toBe(2);
expect(component.editorService.warningText[1]).toBe(err);
})));

it('should handle an network error from a namespace apply',
fakeAsync(
inject([HttpTestingController],
(backend: HttpTestingController) => {
const ns = 'foo';
const md = new YipeeFileMetadata();
md.name = ns;
md.flatFile.appInfo.namespace = ns;

component.editorService.metadata = md;

component.onApplyManifestClicked();

expect(component.editorService.warningText.length).toBe(0);
backend.expectOne({method: 'POST', url: '/api/namespaces/apply/' + ns})
.error(new ErrorEvent('Network issue'));
tick(50);
expect(component.editorService.warningText.length).toBe(2);
})));

it('should throw an error if namespace is empty', fakeAsync(inject([HttpTestingController, EditorService], (backend: HttpTestingController, service: EditorService) => {
const ns = 'namespace';
service.metadata = new YipeeFileMetadata();
service.metadata.name = ns;

expect(component.editorService.warningText.length).toBe(0);
component.onApplyManifestClicked();
tick(50);
expect(component.editorService.warningText.length).toBe(2);
expect(component.editorService.warningText[1]).toBe(ApiService.MISSING_NAMESPACE);

})));



});
25 changes: 25 additions & 0 deletions ui/src/app/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { YipeeFileService } from '../shared/services/yipee-file.service';
styleUrls: ['./editor.component.css']
})
export class EditorComponent implements OnInit, AfterViewChecked {
static APPLY_FAIL = 'Failure applying kubernetes manifest. - ';
static APPLY_SUCCESS = 'Successfully applied kubernetes manifest.';

showWarningModal: boolean;

Expand All @@ -27,6 +29,8 @@ export class EditorComponent implements OnInit, AfterViewChecked {
disregardChanges = false;
resizing = false;
viewType = 'app';
isDashboard: boolean;
isApplyingManifest = false;

ui = {
loading: true,
Expand Down Expand Up @@ -178,4 +182,25 @@ export class EditorComponent implements OnInit, AfterViewChecked {
}
}


onApplyManifestClicked() {
this.isApplyingManifest = true;
this.editorService.applyManifest()
.subscribe((response: Response) => {
this.editorService.infoText.length = 0;
this.editorService.infoText.push(EditorComponent.APPLY_SUCCESS);
this.isApplyingManifest = false;
}, (err) => {
this.isApplyingManifest = false;
this.editorService.warningText.length = 0;
// a network error won't have the error text so we need to guard against that
if (err.error && err.error.data) {
this.editorService.warningText.push(EditorComponent.APPLY_FAIL, err.error.data[0]);
} else {
this.editorService.warningText.push(EditorComponent.APPLY_FAIL, err.message);
}
});
}


}
5 changes: 5 additions & 0 deletions ui/src/app/editor/editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,4 +312,9 @@ export class EditorService {
this._dirty = value;
}

applyManifest(): Observable<any> {
return this.yipeeFileService.applyManifest(this.metadata, this.metadata.flatFile.appInfo.createNs);
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@
</span>
</label>
</div>
<div style="padding-left: 4rem;" class="form-group">
<label></label>
<div class="toggle-switch">
<!-- <input type="checkbox" formControlName="createNs" (click)="createNsClick()"> -->
<input type="checkbox" id="createNs" formControlName="createNs" >
<label for="createNs">Create namespace on apply?</label>
</div>
</div>

<!-- END application namespace -->

<!-- application description -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,7 @@ export class K8sInfoContainerComponent implements OnInit {
CustomValidators.startsWithDash,
CustomValidators.endsWithDash
]],
createNs: [{ value: this.k8sFile.appInfo.createNs, disabled: this.isReadOnly }, []],
description: [{ value: this.k8sFile.appInfo.description, disabled: this.isReadOnly }, [ /* add validations here */]],
readme: [{ value: this.k8sFile.appInfo.readme, disabled: this.isReadOnly }, [ /* add validations here */]],
helmSettingsAll: [{ value: this.k8sFile.appInfo.helmSettingsAll, disabled: this.isReadOnly }, [ /* no validations */ ]],
Expand All @@ -266,6 +267,7 @@ export class K8sInfoContainerComponent implements OnInit {
// TODO: if anyone knows any better shorthand for all of these value changes please impliment it, just make sure you do all of them.
this.form.get('name').valueChanges.subscribe((newVal) => this.k8sFile.appInfo.name = newVal);
this.form.get('namespace').valueChanges.subscribe((newVal) => this.k8sFile.appInfo.namespace = newVal);
this.form.get('createNs').valueChanges.subscribe((newVal) => this.k8sFile.appInfo.createNs = newVal);
this.form.get('description').valueChanges.subscribe((newVal) => this.k8sFile.appInfo.description = newVal);
this.form.get('readme').valueChanges.subscribe((newVal) => this.k8sFile.appInfo.readme = newVal);
this.form.get('helmSettingsAll').valueChanges.subscribe((newVal) => this.k8sFile.appInfo.helmSettingsAll = newVal);
Expand Down
6 changes: 6 additions & 0 deletions ui/src/app/models/YipeeFileResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,9 @@ export interface YipeeFileErrorResponse {
total: number;
data: string[];
}

export interface YipeeResponse {
success: boolean;
total: number;
data: any[];
}
1 change: 1 addition & 0 deletions ui/src/app/models/common/AppInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class AppInfo extends ParsedObject {
helmSettingsPorts: boolean;
readme: string;
model_id: string;
createNs = false;

public static construct(type: string): ParsedObject {
return new AppInfo();
Expand Down
Loading

0 comments on commit d0bf1b0

Please sign in to comment.