From a01e9bab637bf398bcb1755a8f1fad506273c92f Mon Sep 17 00:00:00 2001 From: RINO767 Date: Mon, 15 Jul 2019 13:05:04 +0200 Subject: [PATCH 01/20] implemented caching of editor state --- src/app/guards/http-error.interceptor.ts | 1 + src/app/model/EditorStorageState.ts | 6 ++ src/app/root-store/editor-store/actions.ts | 19 +++++- src/app/root-store/editor-store/reducer.ts | 2 + src/app/root-store/editor-store/selectors.ts | 11 +++ src/app/services/cloud-data.service.ts | 6 +- src/app/services/editor.service.ts | 72 ++++++++++++++++++-- 7 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 src/app/model/EditorStorageState.ts diff --git a/src/app/guards/http-error.interceptor.ts b/src/app/guards/http-error.interceptor.ts index a5112df..d80a8f5 100644 --- a/src/app/guards/http-error.interceptor.ts +++ b/src/app/guards/http-error.interceptor.ts @@ -38,6 +38,7 @@ export class HttpErrorInterceptor implements HttpInterceptor { return of(null); } + console.log('error status') this.toastService.show({text: 'Application Error', type: ToastType.DANGER}); return of(null); })); diff --git a/src/app/model/EditorStorageState.ts b/src/app/model/EditorStorageState.ts new file mode 100644 index 0000000..5b626b1 --- /dev/null +++ b/src/app/model/EditorStorageState.ts @@ -0,0 +1,6 @@ +export interface EditorStorageState { + editorValue: string; + filename: string; + JobId: string; + queueId: string; +} diff --git a/src/app/root-store/editor-store/actions.ts b/src/app/root-store/editor-store/actions.ts index a6b8db7..f85c3ee 100644 --- a/src/app/root-store/editor-store/actions.ts +++ b/src/app/root-store/editor-store/actions.ts @@ -1,7 +1,9 @@ import {Action} from '@ngrx/store'; import {Job, Queue} from 'cloudiator-rest-api'; +import {State} from './state'; +export const SET_STATE = '[Editor] set State'; export const SET_VALUE = '[Editor] set Value'; export const SET_FILENAME = '[Editor] set Filename'; export const SET_EDITOR_GRAPH = '[Editor] set editorGraph'; @@ -10,6 +12,13 @@ export const SET_EDITOR_QUEUE = '[Editor] set editorQueue'; export const UPLOAD_FILE = '[Editor] upload File'; export const CHANGES_SAVED = '[Editor] changes saved'; +export class SetEditorState implements Action { + readonly type = SET_STATE; + + constructor(public state: State) { + } +} + export class SetValueAction implements Action { readonly type = SET_VALUE; @@ -60,4 +69,12 @@ export class ChangesSavedAction implements Action { } } -export type All = SetValueAction | SetFilenameAction | SetEditorGraphAction | SetEditorJobAction | SetEditorQueueAction | UploadFileAction | ChangesSavedAction; +export type All = + SetEditorState + | SetValueAction + | SetFilenameAction + | SetEditorGraphAction + | SetEditorJobAction + | SetEditorQueueAction + | UploadFileAction + | ChangesSavedAction; diff --git a/src/app/root-store/editor-store/reducer.ts b/src/app/root-store/editor-store/reducer.ts index 8f90749..3b48445 100644 --- a/src/app/root-store/editor-store/reducer.ts +++ b/src/app/root-store/editor-store/reducer.ts @@ -3,6 +3,8 @@ import {initialState, State} from './state'; export function editorReducer(state = initialState, action: editorActions.All): State { switch (action.type) { + case editorActions.SET_STATE: + return action.state; case editorActions.SET_VALUE: return {...state, value: action.value, unsavedChanges: action.value !== state.originalValue}; case editorActions.SET_FILENAME: diff --git a/src/app/root-store/editor-store/selectors.ts b/src/app/root-store/editor-store/selectors.ts index 092c7e3..f95b5c6 100644 --- a/src/app/root-store/editor-store/selectors.ts +++ b/src/app/root-store/editor-store/selectors.ts @@ -1,6 +1,15 @@ import {State} from './state'; import {createFeatureSelector, createSelector, MemoizedSelector} from '@ngrx/store'; +import {EditorStorageState} from '../../model/EditorStorageState'; +const getStorageState = (state: State): EditorStorageState => { + return { + editorValue: state.value, + filename: state.filename, + JobId: state.editorJob ? state.editorJob.id : undefined, + queueId: state.editorQueue ? state.editorQueue.id : undefined + }; +}; const getValue = (state: State): string => state.value; const getFilename = (state: State): string => state.filename; const hasUnsavedChanges = (state: State): boolean => state.unsavedChanges; @@ -11,6 +20,8 @@ const getEditorQueue = (state: State): any | null => state.editorQueue; export const selectEditorState: MemoizedSelector = createFeatureSelector('editor'); +export const selectStorageState: MemoizedSelector = + createSelector(selectEditorState, getStorageState); export const selectValue: MemoizedSelector = createSelector(selectEditorState, getValue); export const selectFilename: MemoizedSelector diff --git a/src/app/services/cloud-data.service.ts b/src/app/services/cloud-data.service.ts index 7bab40c..1ca9ae5 100644 --- a/src/app/services/cloud-data.service.ts +++ b/src/app/services/cloud-data.service.ts @@ -2,7 +2,7 @@ import {Injectable} from '@angular/core'; import {Cloud, CloudService, Hardware, Image, NewCloud, Location} from 'cloudiator-rest-api'; import {Observable, of, pipe} from 'rxjs'; import {select, Store} from '@ngrx/store'; -import {finalize, map, take, timeout} from 'rxjs/operators'; +import {catchError, finalize, map, take, timeout} from 'rxjs/operators'; import {HttpResponse} from '@angular/common/http'; import {RuntimeConfigService} from './runtime-config.service'; import {ToastService} from '../app-dialog/services/toast.service'; @@ -162,6 +162,7 @@ export class CloudDataService { this.store.dispatch(new CloudDataActions.SetCloudsAction(clouds)); }, err => { + this.store.dispatch(new CloudDataActions.SetCloudIsLoading(false)); console.error('could not fetch clouds', err); this.toastService.show({text: 'could not fetch clouds', type: ToastType.DANGER}, false); }, @@ -183,6 +184,7 @@ export class CloudDataService { this.store.dispatch(new CloudDataActions.SetHardwareAction(hardware)); }, () => { + this.store.dispatch(new CloudDataActions.SetHardwareIsLoading(false)); console.error('could not fetch Hardware'); this.toastService.show({text: 'could not fetch Hardware', type: ToastType.DANGER}, false); }, @@ -203,6 +205,7 @@ export class CloudDataService { this.store.dispatch(new CloudDataActions.SetImagesAction(images)); }, () => { + this.store.dispatch(new CloudDataActions.SetImageIsLoading(false)); console.error('could not fetch Images'); this.toastService.show({text: 'could not fetch Images', type: ToastType.DANGER}, false); }, @@ -223,6 +226,7 @@ export class CloudDataService { this.store.dispatch(new CloudDataActions.SetLocationsAction(locations)); }, () => { + this.store.dispatch(new CloudDataActions.SetLocationIsLoading(false)); console.error('could not fetch Images'); this.toastService.show({text: 'could not fetch Images', type: ToastType.DANGER}, false); }, diff --git a/src/app/services/editor.service.ts b/src/app/services/editor.service.ts index 8c1999c..3c36239 100644 --- a/src/app/services/editor.service.ts +++ b/src/app/services/editor.service.ts @@ -1,25 +1,36 @@ -import {Injectable} from '@angular/core'; +import {Injectable, OnInit} from '@angular/core'; import {select, Store} from '@ngrx/store'; import saveAs from 'file-saver'; -import {Observable} from 'rxjs'; -import {filter, mergeMap, take} from 'rxjs/operators'; +import {Observable, zip} from 'rxjs'; +import {filter, mergeMap, take, tap} from 'rxjs/operators'; import {EditorActions, EditorSelectors, RootStoreState} from '../root-store'; import {JobDataService} from './job-data.service'; import {Job, Queue} from 'cloudiator-rest-api'; import {QueueDataService} from './queue-data.service'; +import {EditorStorageState} from '../model/EditorStorageState'; /** - * Service handling all Functionallity of the Editor View. + * Service handling all Functionality of the Editor View. */ @Injectable({ providedIn: 'root' }) export class EditorService { + private readonly EDITOR_STORE_KEY = 'editor'; + /** @ignore */ constructor(public store: Store, public jobDataService: JobDataService, public queueDataService: QueueDataService) { + const storedState = this.loadEditorState(); + if (storedState) { + this.composeStorageState(storedState); + } + + this.store.pipe(select(EditorSelectors.selectStorageState)).subscribe(val => { + this.saveEditorState(val); + }); } /** @@ -84,7 +95,7 @@ export class EditorService { */ setEditorQueue(queue: Queue) { if (queue) { - this.queueDataService.listenToQueueTaskStatus(queue.id); + this.queueDataService.listenToQueueTaskStatus(queue.id); } this.store.dispatch(new EditorActions.SetEditorQueueAction(queue)); } @@ -153,4 +164,55 @@ export class EditorService { } return Promise.reject('could not load file'); } + + /** + * Composes needed information to save the editor state from the localStorage into the redux store. + * This includes fetching job and queue if existing. + * @param {EditorStorageState} storedState + */ + private composeStorageState(storedState: EditorStorageState) { + this.store.dispatch(new EditorActions.UploadFileAction( + storedState.editorValue, + storedState.filename + )); + // fetch job if existing + if (storedState.JobId) { + this.jobDataService.findJob(storedState.JobId) + .pipe(filter(v => v !== undefined), + take(1)) + .subscribe(job => { + this.store.dispatch(new EditorActions.SetEditorJobAction(job)); + // fetch job graph if job was found + this.jobDataService.jobGraph(job.id) + .subscribe(graph => + this.store.dispatch(new EditorActions.SetEditorGraphAction(graph))); + } + ); + } + // fetch queue + if (storedState.queueId) { + this.queueDataService.findQueuedTask(storedState.queueId) + .subscribe(queue => + this.store.dispatch(new EditorActions.SetEditorQueueAction(queue)) + ); + } + } + + private saveEditorState(state: EditorStorageState) { + localStorage.setItem(this.EDITOR_STORE_KEY, JSON.stringify(state)); + } + + private loadEditorState(): EditorStorageState { + const parsed: EditorStorageState = JSON.parse(localStorage.getItem(this.EDITOR_STORE_KEY)); + return { + editorValue: parsed.editorValue ? parsed.editorValue : '', + filename: parsed.filename ? parsed.filename : 'unnamed.yaml', + JobId: parsed.JobId ? parsed.JobId : null, + queueId: parsed.queueId ? parsed.queueId : null + }; + } + + private removeEditorState() { + localStorage.removeItem(this.EDITOR_STORE_KEY); + } } From 70ed1a24d2294ce5ce3df7951e992b496ec52a07 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Mon, 22 Jul 2019 08:30:25 +0200 Subject: [PATCH 02/20] fixed tests for editor caching --- .../yaml-editor/yaml-editor.component.spec.ts | 2 +- src/app/services/editor.service.spec.ts | 2 ++ src/app/services/editor.service.ts | 15 ++++++++------- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/app/components/editor/yaml-editor/yaml-editor.component.spec.ts b/src/app/components/editor/yaml-editor/yaml-editor.component.spec.ts index 38f21fc..d175b2a 100644 --- a/src/app/components/editor/yaml-editor/yaml-editor.component.spec.ts +++ b/src/app/components/editor/yaml-editor/yaml-editor.component.spec.ts @@ -76,7 +76,7 @@ describe('YamlEditorComponent', () => { spyOn(component.editorService, 'getFilename').and.callThrough(); component.editor.setValue('test'); - expect(component.editorService.setEditorValue).toHaveBeenCalledTimes(1); + expect(component.editorService.setEditorValue).toHaveBeenCalledTimes(2); expect(component.editorService.getEditorValue).not.toHaveBeenCalled(); component.editorService.getEditorValue() .pipe(take(1)) diff --git a/src/app/services/editor.service.spec.ts b/src/app/services/editor.service.spec.ts index 0d462aa..7e8b160 100644 --- a/src/app/services/editor.service.spec.ts +++ b/src/app/services/editor.service.spec.ts @@ -29,6 +29,8 @@ describe('EditorService', () => { it('save state should be consistent', async () => { const service: EditorService = TestBed.get(EditorService); + service.store.dispatch(new EditorActions.UploadFileAction('', 'unnamed.yaml')); + service.HasUnsaveChanges() .pipe(take(1)) .subscribe(value => expect(value).toBeFalsy()); diff --git a/src/app/services/editor.service.ts b/src/app/services/editor.service.ts index 3c36239..9481f7d 100644 --- a/src/app/services/editor.service.ts +++ b/src/app/services/editor.service.ts @@ -170,7 +170,7 @@ export class EditorService { * This includes fetching job and queue if existing. * @param {EditorStorageState} storedState */ - private composeStorageState(storedState: EditorStorageState) { + public composeStorageState(storedState: EditorStorageState) { this.store.dispatch(new EditorActions.UploadFileAction( storedState.editorValue, storedState.filename @@ -204,12 +204,13 @@ export class EditorService { private loadEditorState(): EditorStorageState { const parsed: EditorStorageState = JSON.parse(localStorage.getItem(this.EDITOR_STORE_KEY)); - return { - editorValue: parsed.editorValue ? parsed.editorValue : '', - filename: parsed.filename ? parsed.filename : 'unnamed.yaml', - JobId: parsed.JobId ? parsed.JobId : null, - queueId: parsed.queueId ? parsed.queueId : null - }; + return parsed ? + { + editorValue: parsed.editorValue ? parsed.editorValue : '', + filename: parsed.filename ? parsed.filename : 'unnamed.yaml', + JobId: parsed.JobId ? parsed.JobId : null, + queueId: parsed.queueId ? parsed.queueId : null + } : null; } private removeEditorState() { From 0bb1c28fafac899c4001e6d9f8aae6e863dbc9dc Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Mon, 22 Jul 2019 08:51:29 +0200 Subject: [PATCH 03/20] fixed login button being stuck in animation on specific errors --- src/app/components/login/login.component.ts | 19 ++++++++++++++----- src/app/guards/http-error.interceptor.ts | 5 ++++- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index ef3b492..84cf348 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -49,13 +49,22 @@ export class LoginComponent implements OnInit { onLogin() { this.isLoggingIn = true; this.authService.logIn(this.login).subscribe(success => { - this.isLoggingIn = false; - if (success) { - this.router.navigate(['/']); - } else { + this.isLoggingIn = false; + if (success) { + this.router.navigate(['/']); + } else { + this.logInError = true; + } + }, + () => { + this.isLoggingIn = false; + this.logInError = true; + }, + () => { + this.isLoggingIn = false; this.logInError = true; } - }); + ); } } diff --git a/src/app/guards/http-error.interceptor.ts b/src/app/guards/http-error.interceptor.ts index a5112df..8136595 100644 --- a/src/app/guards/http-error.interceptor.ts +++ b/src/app/guards/http-error.interceptor.ts @@ -1,6 +1,6 @@ import {Injectable} from '@angular/core'; import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; -import {catchError, tap} from 'rxjs/operators'; +import {catchError} from 'rxjs/operators'; import {Observable, of, throwError} from 'rxjs'; import {AuthService} from '../services/auth.service'; import {ToastService} from '../app-dialog/services/toast.service'; @@ -32,6 +32,9 @@ export class HttpErrorInterceptor implements HttpInterceptor { case 504: this.toastService.show({text: 'Gateway timeout', type: ToastType.DANGER}); break; + case 0: + this.toastService.show({text: 'Connection refused', type: ToastType.DANGER}); + break; default: return throwError(err); } From 9165fde9ec2c0d2322ad3027215d3db92fb1a76c Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Mon, 22 Jul 2019 09:03:31 +0200 Subject: [PATCH 04/20] fixed ngrx/store runtimechecks message --- src/app/root-store/root-store.module.ts | 29 ++++++++++++++++--------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/app/root-store/root-store.module.ts b/src/app/root-store/root-store.module.ts index 0c5a8a8..e0aa3d4 100644 --- a/src/app/root-store/root-store.module.ts +++ b/src/app/root-store/root-store.module.ts @@ -1,18 +1,26 @@ -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; import {StoreModule} from '@ngrx/store'; -import { RuntimeConfigStoreModule } from './runtime-config-store/runtime-config-store.module'; -import { CloudDataStoreModule } from './cloud-data-store/cloud-data-store.module'; -import { EditorStoreModule } from './editor-store/editor-store.module'; -import { JobDataStoreModule } from './job-data-store/job-data-store.module'; -import { AuthStoreModule } from './auth-store/auth-store.module'; -import { ProcessDataStoreModule } from './process-data-store/process-data-store.module'; +import {RuntimeConfigStoreModule} from './runtime-config-store/runtime-config-store.module'; +import {CloudDataStoreModule} from './cloud-data-store/cloud-data-store.module'; +import {EditorStoreModule} from './editor-store/editor-store.module'; +import {JobDataStoreModule} from './job-data-store/job-data-store.module'; +import {AuthStoreModule} from './auth-store/auth-store.module'; +import {ProcessDataStoreModule} from './process-data-store/process-data-store.module'; @NgModule({ declarations: [], imports: [ CommonModule, - StoreModule.forRoot({}), + StoreModule.forRoot({}, { + runtimeChecks: { + strictStateImmutability: false, + strictActionImmutability: false, + strictStateSerializability: false, + strictActionSerializability: false, + }, + } + ), RuntimeConfigStoreModule, CloudDataStoreModule, EditorStoreModule, @@ -21,4 +29,5 @@ import { ProcessDataStoreModule } from './process-data-store/process-data-store. ProcessDataStoreModule ] }) -export class RootStoreModule { } +export class RootStoreModule { +} From 435d9dbb8261edf3ba80955cccb3c3bccb5f0d34 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Mon, 22 Jul 2019 10:34:47 +0200 Subject: [PATCH 05/20] trying to debugg ci problem --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ac469bc..21045c7 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "start": "ng serve", "build": "ng build", "test": "ng test --browsers=Chrome --source-map=false", - "travis-test": "ng test --watch=false --environment=prod --browsers=ChromeHeadlessNoSandbox --reporters=progress,kjhtml", + "travis-test": "ng test --watch=false --environment=prod --browsers=ChromeHeadlessNoSandbox --reporters=progress,kjhtml --source-map=false", "lint": "ng lint", "e2e": "ng e2e", "compodoc": "npx compodoc", From 3c830e37feff230baffab7e0b1e11c07e6e70cc5 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Wed, 24 Jul 2019 12:55:37 +0200 Subject: [PATCH 06/20] implemented Delete button for schedule view Changed cloudview deletebutton to trash can --- package.json | 4 +- src/app/app-dialog/app-dialog.module.ts | 7 +- .../delete-schedule-dialog.component.html | 16 ++ .../delete-schedule-dialog.component.scss | 0 .../delete-schedule-dialog.component.spec.ts | 31 +++ .../delete-schedule-dialog.component.ts | 24 +++ .../cloud-view/cloud-view.component.html | 194 +++++++++--------- .../cloud-view/cloud-view.component.scss | 11 + .../schedules-overview.component.html | 3 +- .../schedules-view.component.html | 6 + .../schedules-view.component.scss | 13 ++ .../schedules-view.component.spec.ts | 3 + .../schedules-view.component.ts | 62 +++--- src/app/model/EditorStorageState.ts | 2 +- src/app/root-store/editor-store/selectors.ts | 2 +- src/app/services/editor.service.ts | 10 +- src/app/services/job-data.service.ts | 11 +- src/app/services/process-data.service.ts | 7 + testing/test-data.ts | 46 +++-- 19 files changed, 296 insertions(+), 156 deletions(-) create mode 100644 src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.html create mode 100644 src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.scss create mode 100644 src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.spec.ts create mode 100644 src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.ts diff --git a/package.json b/package.json index 21045c7..aa6be98 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "@angular/core": "^8.1.1", "@angular/flex-layout": "^8.0.0-beta.26", "@angular/forms": "^8.1.1", - "@angular/http": "*", + "@angular/http": "7.2.15", "@angular/platform-browser": "^8.1.1", "@angular/platform-browser-dynamic": "^8.1.1", "@angular/pwa": "^0.801.1", @@ -33,7 +33,7 @@ "bulma-badge": "^2.0.0", "bulma-checkradio": "^2.1.1", "bulma-divider": "^2.0.1", - "cloudiator-rest-api": "1.4.0", + "cloudiator-rest-api": "1.5.0", "core-js": "^2.6.5", "cytoscape": "^3.8.1", "file-saver": "^2.0.1", diff --git a/src/app/app-dialog/app-dialog.module.ts b/src/app/app-dialog/app-dialog.module.ts index be06963..428bf5c 100644 --- a/src/app/app-dialog/app-dialog.module.ts +++ b/src/app/app-dialog/app-dialog.module.ts @@ -5,6 +5,7 @@ import {DialogService} from './services/dialog.service'; import {ToastService} from './services/toast.service'; import {ConfirmNewCloudDialogComponent} from './dialogs/confirm-new-cloud-dialog/confirm-new-cloud-dialog.component'; import {DeleteCloudDialogComponent} from './dialogs/delete-cloud-dialog/delete-cloud-dialog.component'; +import { DeleteScheduleDialogComponent } from './dialogs/delete-schedule-dialog/delete-schedule-dialog.component'; /** * Main Module handling App DIalogs and Toasts. @@ -13,7 +14,8 @@ import {DeleteCloudDialogComponent} from './dialogs/delete-cloud-dialog/delete-c declarations: [ ToastComponent, ConfirmNewCloudDialogComponent, - DeleteCloudDialogComponent + DeleteCloudDialogComponent, + DeleteScheduleDialogComponent ], imports: [ OverlayModule @@ -25,7 +27,8 @@ import {DeleteCloudDialogComponent} from './dialogs/delete-cloud-dialog/delete-c entryComponents: [ ToastComponent, ConfirmNewCloudDialogComponent, - DeleteCloudDialogComponent + DeleteCloudDialogComponent, + DeleteScheduleDialogComponent ] }) export class AppDialogModule { diff --git a/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.html b/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.html new file mode 100644 index 0000000..2488498 --- /dev/null +++ b/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.html @@ -0,0 +1,16 @@ + diff --git a/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.scss b/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.spec.ts b/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.spec.ts new file mode 100644 index 0000000..dc94ea0 --- /dev/null +++ b/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.spec.ts @@ -0,0 +1,31 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeleteScheduleDialogComponent } from './delete-schedule-dialog.component'; +import {DialogRef} from '../../model/dialogRef'; +import {DIALOG_DATA} from '../../services/dialog.service'; + +describe('DeleteScheduleDialogComponent', () => { + let component: DeleteScheduleDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DeleteScheduleDialogComponent ], + providers: [ + {provide: DialogRef, useVale: {}}, + { provide: DIALOG_DATA, useValue: {}} + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteScheduleDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.ts b/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.ts new file mode 100644 index 0000000..7a950d7 --- /dev/null +++ b/src/app/app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component.ts @@ -0,0 +1,24 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {DialogRef} from '../../model/dialogRef'; +import {DIALOG_DATA} from '../../services/dialog.service'; + +@Component({ + selector: 'app-delete-schedule-dialog', + templateUrl: './delete-schedule-dialog.component.html', + styleUrls: ['./delete-schedule-dialog.component.scss'] +}) +export class DeleteScheduleDialogComponent implements OnInit { + + public scheduleName: string; + + constructor(public dialogRef: DialogRef, + @Inject(DIALOG_DATA) public data: any) { } + + ngOnInit() { + this.scheduleName = this.data.scheduleName; + } + + public onClose(result = false) { + this.dialogRef.close(result); + } +} diff --git a/src/app/components/clouds/cloud-view/cloud-view.component.html b/src/app/components/clouds/cloud-view/cloud-view.component.html index f8c9e21..438b8cc 100644 --- a/src/app/components/clouds/cloud-view/cloud-view.component.html +++ b/src/app/components/clouds/cloud-view/cloud-view.component.html @@ -1,117 +1,121 @@ -
-
-
-
-
-

- {{cloud.api.providerName}} -

-

- {{cloud.credential.user}} -

-
+
+
+
+
+
+

+ {{cloud.api.providerName}} +

+

+ {{cloud.credential.user}} +

+
-
-
+
+
+ + +
+
-
+
-
-
- -
-
- Hardware -
-
- +
+
+ +
+
+ Hardware +
+
+
- - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - -
- Name - {{row.name}} + Name + {{row.name}} - Ram - {{row.ram}} + Ram + {{row.ram}} - Cores - {{row.cores}} + Cores + {{row.cores}} - Disk - {{row.disk}} + Disk + {{row.disk}}
-
+ + + +
+
- -
-
Images -
-
- + +
+
Images +
+
+
- - - - - + + + + + - - - - - + + + + + - - - -
- Name - {{row.name}} + Name + {{row.name}} - OS - {{row.operatingSystem ? row.operatingSystem.operatingSystemFamily : - ''}} - + OS + {{row.operatingSystem ? row.operatingSystem.operatingSystemFamily : + ''}} +
-
+ + + +
- +
+ diff --git a/src/app/components/clouds/cloud-view/cloud-view.component.scss b/src/app/components/clouds/cloud-view/cloud-view.component.scss index 3183304..0946c4f 100644 --- a/src/app/components/clouds/cloud-view/cloud-view.component.scss +++ b/src/app/components/clouds/cloud-view/cloud-view.component.scss @@ -7,3 +7,14 @@ th { max-height: 300px; overflow-y: auto; } + +.delete-button { + width: 30px; + height: 30px; + cursor: pointer; + margin-right: 20px; + border-radius: 100%; + display: flex; + justify-content: center; + align-items: center; +} diff --git a/src/app/components/schedules/schedules-overview/schedules-overview.component.html b/src/app/components/schedules/schedules-overview/schedules-overview.component.html index 246f23e..b7fa0fd 100644 --- a/src/app/components/schedules/schedules-overview/schedules-overview.component.html +++ b/src/app/components/schedules/schedules-overview/schedules-overview.component.html @@ -33,7 +33,8 @@

Schedules

+ [class.is-active]="view.schedule.id === activeViewId" + style="cursor: pointer"> {{view.job?.name}}
diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.html b/src/app/components/schedules/schedules-view/schedules-view.component.html index 4132963..b3ec355 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.html +++ b/src/app/components/schedules/schedules-view/schedules-view.component.html @@ -22,4 +22,10 @@ {{scheduleView?.job?.name}}
+ +
+ + + +
diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.scss b/src/app/components/schedules/schedules-view/schedules-view.component.scss index 9e492ca..5da6921 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.scss +++ b/src/app/components/schedules/schedules-view/schedules-view.component.scss @@ -11,6 +11,19 @@ //border: 1.5px solid $white-ter; } +.delete-button { + width: 30px; + height: 30px; + cursor: pointer; + position: absolute; + top: 10px; + right: 10px; + border-radius: 100%; + display: flex; + justify-content: center; + align-items: center; +} + @include tablet { .view-header { padding: 0.6rem 0.8rem; diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.spec.ts b/src/app/components/schedules/schedules-view/schedules-view.component.spec.ts index 007ce24..cd35189 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.spec.ts +++ b/src/app/components/schedules/schedules-view/schedules-view.component.spec.ts @@ -8,6 +8,7 @@ import {HttpClientModule} from '@angular/common/http'; import {RootStoreModule} from '../../../root-store'; import {AppDialogModule} from '../../../app-dialog/app-dialog.module'; import {SchedulesBottomSheetComponent} from '../schedules-bottom-sheet/schedules-bottom-sheet.component'; +import {RouterTestingModule} from '@angular/router/testing'; describe('SchedulesViewComponent', () => { let component: SchedulesViewComponent; @@ -24,6 +25,8 @@ describe('SchedulesViewComponent', () => { BrowserAnimationsModule, ApiModule.forRoot(apiConfigFactory), HttpClientModule, + AppDialogModule, + RouterTestingModule, AppDialogModule ] }) diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.ts b/src/app/components/schedules/schedules-view/schedules-view.component.ts index 9be58d0..4e15557 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.ts +++ b/src/app/components/schedules/schedules-view/schedules-view.component.ts @@ -1,15 +1,12 @@ -import { - Component, - Input, - OnChanges, - OnInit, - SimpleChanges -} from '@angular/core'; +import {Component, Input, OnChanges, OnInit, SimpleChanges} from '@angular/core'; import {ScheduleView} from '../../../model/ScheduleView'; import * as cytoscape from 'cytoscape'; import {ProcessDataService} from '../../../services/process-data.service'; -import {map, take} from 'rxjs/operators'; -import * as testData from 'testing/test-data'; +import {DialogService} from '../../../app-dialog/services/dialog.service'; +import {ToastService} from '../../../app-dialog/services/toast.service'; +import {ToastType} from '../../../app-dialog/model/toast'; +import {Router} from '@angular/router'; +import {DeleteScheduleDialogComponent} from '../../../app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component'; /** * View of a selected Schedule, containing a Cytoscape and a bottomsheet for further information @@ -75,17 +72,6 @@ export class SchedulesViewComponent implements OnInit, OnChanges { 'width': '5px', 'overlay-padding': '3px' } - }, { - 'selector': 'edge:active', - 'style': { - // 'line-color': 'white', - // 'border-color': 'transparent' - } - }, { - 'selector': 'edge:selected', - 'style': { - 'line-color': 'lightgray' - } }]; /** @@ -134,17 +120,6 @@ export class SchedulesViewComponent implements OnInit, OnChanges { this.cy.center(); this.cy.panBy({x: 0, y: -100}); }); - this.cy.on('select', 'edge', event => { - const target = event.target._private; - this.selected = { - group: 'edges', - data: { - id: target.data.id, - source: target.source._private.data, - target: target.target._private.data - } - }; - }); this.cy.on('select', 'node', event => { this.selected = { group: 'nodes', @@ -161,7 +136,10 @@ export class SchedulesViewComponent implements OnInit, OnChanges { } /** @ignore */ - constructor(private processDataService: ProcessDataService) { + constructor(private processDataService: ProcessDataService, + private dialogSerivce: DialogService, + private toastService: ToastService, + private router: Router) { } /** @ignore */ @@ -213,4 +191,24 @@ export class SchedulesViewComponent implements OnInit, OnChanges { } ); } + + onDelete() { + this.dialogSerivce.open(DeleteScheduleDialogComponent, {data: { + scheduleName: this.scheduleView.job.name + }}) + .afterClosed() + .subscribe(confirmation => { + if (confirmation) { + this.processDataService.deleteSchedule(this.scheduleView.schedule.id) + .subscribe( + () => { + this.router.navigateByUrl('/schedules'); + }, + err => { + console.error(err); + this.toastService.show({text: 'Could not delete Schedule', type: ToastType.DANGER}); + }); + } + }); + } } diff --git a/src/app/model/EditorStorageState.ts b/src/app/model/EditorStorageState.ts index 5b626b1..ea04ef0 100644 --- a/src/app/model/EditorStorageState.ts +++ b/src/app/model/EditorStorageState.ts @@ -1,6 +1,6 @@ export interface EditorStorageState { editorValue: string; filename: string; - JobId: string; + jobId: string; queueId: string; } diff --git a/src/app/root-store/editor-store/selectors.ts b/src/app/root-store/editor-store/selectors.ts index f95b5c6..a5fc050 100644 --- a/src/app/root-store/editor-store/selectors.ts +++ b/src/app/root-store/editor-store/selectors.ts @@ -6,7 +6,7 @@ const getStorageState = (state: State): EditorStorageState => { return { editorValue: state.value, filename: state.filename, - JobId: state.editorJob ? state.editorJob.id : undefined, + jobId: state.editorJob ? state.editorJob.id : undefined, queueId: state.editorQueue ? state.editorQueue.id : undefined }; }; diff --git a/src/app/services/editor.service.ts b/src/app/services/editor.service.ts index 9481f7d..7eff232 100644 --- a/src/app/services/editor.service.ts +++ b/src/app/services/editor.service.ts @@ -175,10 +175,12 @@ export class EditorService { storedState.editorValue, storedState.filename )); + // fetch job if existing - if (storedState.JobId) { - this.jobDataService.findJob(storedState.JobId) - .pipe(filter(v => v !== undefined), + if (storedState.jobId) { + this.jobDataService.findJob(storedState.jobId) + .pipe( + filter(v => v !== undefined), take(1)) .subscribe(job => { this.store.dispatch(new EditorActions.SetEditorJobAction(job)); @@ -208,7 +210,7 @@ export class EditorService { { editorValue: parsed.editorValue ? parsed.editorValue : '', filename: parsed.filename ? parsed.filename : 'unnamed.yaml', - JobId: parsed.JobId ? parsed.JobId : null, + jobId: parsed.jobId ? parsed.jobId : null, queueId: parsed.queueId ? parsed.queueId : null } : null; } diff --git a/src/app/services/job-data.service.ts b/src/app/services/job-data.service.ts index 2a66f01..6e78e0e 100644 --- a/src/app/services/job-data.service.ts +++ b/src/app/services/job-data.service.ts @@ -3,10 +3,11 @@ import {select, Store} from '@ngrx/store'; import {RuntimeConfigService} from './runtime-config.service'; import {ToastService} from '../app-dialog/services/toast.service'; import {Job, JobService} from 'cloudiator-rest-api'; -import {Observable} from 'rxjs'; +import {Observable, of} from 'rxjs'; import {ToastType} from '../app-dialog/model/toast'; import {JobDataActions, JobDataSelectors, RootStoreState, RuntimeConfigSelectors} from '../root-store'; -import {find, first, map} from 'rxjs/operators'; +import {find, first, map, tap} from 'rxjs/operators'; +import * as testData from 'testing/test-data'; /** * Handles al functionality concerning Jobs. @@ -29,8 +30,8 @@ export class JobDataService { */ public findJobs(): Observable { this.fetchJobs(); - return this.store.pipe(select(JobDataSelectors.selectJobs)); + // return of(testData.allJobs) } /** @@ -39,7 +40,9 @@ export class JobDataService { * @return {Observable} */ public findJob(id: string): Observable { - return this.findJobs().pipe(map(jobs => jobs.find(job => job.id === id))); + this.fetchJobs(); + return this.findJobs().pipe( + map(jobs => jobs.find(job => job.id === id))); } /** diff --git a/src/app/services/process-data.service.ts b/src/app/services/process-data.service.ts index bf314d8..6fa558c 100644 --- a/src/app/services/process-data.service.ts +++ b/src/app/services/process-data.service.ts @@ -46,6 +46,7 @@ export class ProcessDataService { public getSchedules(): Observable { this.fetchSchedules(); return this.store.pipe(select(ProcessDataSelectors.selectSchedules)); + // return of(testData.allSchedules) } public getScheduleIsLoading(): Observable { @@ -60,6 +61,8 @@ export class ProcessDataService { */ scheduleGraph(id: string): Observable { return this.processApiService.scheduleGraph(id) + // return id === '4f1cf465-d420-4d63-a456-88f65981c3cd' ? of(null) : + // of(testData.SchedulesGraph) .pipe(map(graph => { return { edges: graph.edges, @@ -68,6 +71,10 @@ export class ProcessDataService { })); } + deleteSchedule(id: string): Observable { + return this.processApiService.deleteSchedule(id); + } + /** * Fetches schedules from api. */ diff --git a/testing/test-data.ts b/testing/test-data.ts index f74ba6b..b6f4064 100644 --- a/testing/test-data.ts +++ b/testing/test-data.ts @@ -10,10 +10,10 @@ import { Location, Login, OclRequirement, - OperatingSystem, + OperatingSystem, Port, PortProvided, PortRequired, Queue, - Schedule, + Schedule, ServiceBehaviour, SingleProcess, Token } from 'cloudiator-rest-api'; @@ -306,7 +306,7 @@ export const jobTwo: Job = { { name: 'wiki', ports: [ - { + { type: 'PortProvided', name: 'WIKIPROV', port: 80 @@ -357,12 +357,15 @@ export const jobTwo: Job = { constraint: 'nodes->forAll(image.providerId = \'f688f98d-7e62-4404-a672-1fc054fcfa6c\')' } ], - taskType: 'BATCH' + behaviour: { + type: 'ServiceBehaviour', + restart: true + } }, { name: 'database', ports: [ - { + { type: 'PortProvided', name: 'MARIADBPROV', port: 3306 @@ -407,7 +410,10 @@ export const jobTwo: Job = { constraint: 'nodes->forAll(image.providerId = \'f688f98d-7e62-4404-a672-1fc054fcfa6c\')' } ], - taskType: 'BATCH' + behaviour: { + type: 'ServiceBehaviour', + restart: true + } }, { name: 'loadbalancer', @@ -418,7 +424,7 @@ export const jobTwo: Job = { updateAction: './mediawiki-tutorial/scripts/lance/nginx.sh configure', isMandatory: false }, - { + { type: 'PortProvided', name: 'LBPROV', port: 80 @@ -463,7 +469,10 @@ export const jobTwo: Job = { constraint: 'nodes->forAll(image.providerId = \'f688f98d-7e62-4404-a672-1fc054fcfa6c\')' } ], - taskType: 'BATCH' + behaviour: { + type: 'ServiceBehaviour', + restart: true + } } ], communications: [ @@ -576,7 +585,7 @@ export const JobOne: Job = { { name: 'database', ports: [ - { + { type: 'PortProvided', name: 'MARIADBPROV', port: 3306 @@ -617,7 +626,10 @@ export const JobOne: Job = { constraint: 'nodes->forAll(image.providerId = \'ami-0bb24d586ad9956e7\')' } ], - taskType: 'BATCH' + behaviour: { + type: 'ServiceBehaviour', + restart: true + } } ], communications: null, @@ -632,7 +644,7 @@ export const JobTwo: Job = { { name: 'database', ports: [ - { + { type: 'PortProvided', name: 'MARIADBPROV', port: 3306 @@ -673,7 +685,10 @@ export const JobTwo: Job = { constraint: 'nodes->forAll(image.providerId = \'ami-0bb24d586ad9956e7\')' } ], - taskType: 'BATCH' + behaviour: { + type: 'ServiceBehaviour', + restart: true + } } ], communications: null, @@ -694,7 +709,7 @@ export const JobThree: Job = { updateAction: './mediawiki-tutorial/scripts/lance/nginx.sh configure', isMandatory: false }, - { + { type: 'PortProvided', name: 'LBPROV', port: 80 @@ -735,7 +750,10 @@ export const JobThree: Job = { constraint: 'nodes->forAll(image.providerId = \'ami-0bb24d586ad9956e7\')' } ], - taskType: 'BATCH' + behaviour: { + type: 'ServiceBehaviour', + restart: true + } }, { name: 'wiki', From b01bc53eb8711a4a1a5b648d4efb06c6758cb187 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Thu, 25 Jul 2019 09:10:11 +0200 Subject: [PATCH 07/20] fixed inconsistent cache loading --- .../yaml-editor/yaml-editor.component.ts | 20 +-- src/app/model/EditorStorageState.ts | 2 +- src/app/root-store/editor-store/selectors.ts | 2 +- src/app/services/editor.service.spec.ts | 132 +++++++++++++++++- src/app/services/editor.service.ts | 27 ++-- src/app/services/queue-data.service.ts | 11 +- 6 files changed, 163 insertions(+), 31 deletions(-) diff --git a/src/app/components/editor/yaml-editor/yaml-editor.component.ts b/src/app/components/editor/yaml-editor/yaml-editor.component.ts index 3736a28..0b3185d 100644 --- a/src/app/components/editor/yaml-editor/yaml-editor.component.ts +++ b/src/app/components/editor/yaml-editor/yaml-editor.component.ts @@ -149,7 +149,6 @@ export class YamlEditorComponent implements OnInit, OnDestroy { // job is valid and stored this.editorService.setEditorJob(job); this.editorService.setEditorQueue(null); - this.isValidating = false; }, err => { this.isValidating = false; @@ -164,7 +163,9 @@ export class YamlEditorComponent implements OnInit, OnDestroy { } else { this.toastService.show({text: 'Unexpected Error', type: ToastType.DANGER}, true); } - }); + }, + () => + this.isValidating = false); } /** @@ -175,12 +176,13 @@ export class YamlEditorComponent implements OnInit, OnDestroy { this.processDataService.submitEditorSchedule() .pipe(take(1)) .subscribe(queue => { - this.editorService.setEditorQueue(queue); - this.isSubmitting = false; - }, - err => { - this.isSubmitting = false; - this.toastService.show({text: 'Unexpected Error', type: ToastType.DANGER}, true); - }); + this.editorService.setEditorQueue(queue); + }, + err => { + this.isSubmitting = false; + this.toastService.show({text: 'Unexpected Error', type: ToastType.DANGER}, true); + }, + () => + this.isSubmitting = false); } } diff --git a/src/app/model/EditorStorageState.ts b/src/app/model/EditorStorageState.ts index 5b626b1..ea04ef0 100644 --- a/src/app/model/EditorStorageState.ts +++ b/src/app/model/EditorStorageState.ts @@ -1,6 +1,6 @@ export interface EditorStorageState { editorValue: string; filename: string; - JobId: string; + jobId: string; queueId: string; } diff --git a/src/app/root-store/editor-store/selectors.ts b/src/app/root-store/editor-store/selectors.ts index f95b5c6..a5fc050 100644 --- a/src/app/root-store/editor-store/selectors.ts +++ b/src/app/root-store/editor-store/selectors.ts @@ -6,7 +6,7 @@ const getStorageState = (state: State): EditorStorageState => { return { editorValue: state.value, filename: state.filename, - JobId: state.editorJob ? state.editorJob.id : undefined, + jobId: state.editorJob ? state.editorJob.id : undefined, queueId: state.editorQueue ? state.editorQueue.id : undefined }; }; diff --git a/src/app/services/editor.service.spec.ts b/src/app/services/editor.service.spec.ts index 7e8b160..baaec51 100644 --- a/src/app/services/editor.service.spec.ts +++ b/src/app/services/editor.service.spec.ts @@ -1,24 +1,29 @@ import {TestBed} from '@angular/core/testing'; import {EditorService} from './editor.service'; -import {take} from 'rxjs/operators'; +import {filter, take, tap, timeout} from 'rxjs/operators'; import {EditorActions, RootStoreModule} from '../root-store'; -import {HttpClientModule} from '@angular/common/http'; import {ApiModule} from 'cloudiator-rest-api'; import {apiConfigFactory} from '../app.module'; import {AppDialogModule} from '../app-dialog/app-dialog.module'; +import * as testData from 'testing/test-data'; +import {HttpClientTestingModule} from '@angular/common/http/testing'; +import {EditorStorageState} from '../model/EditorStorageState'; +import {queue} from 'rxjs/internal/scheduler/queue'; +import {of} from 'rxjs'; describe('EditorService', () => { beforeEach(() => TestBed.configureTestingModule({ imports: [ RootStoreModule, - HttpClientModule, + HttpClientTestingModule, ApiModule.forRoot(apiConfigFactory), AppDialogModule ] })); beforeEach(() => { + localStorage.clear(); }); it('should be created', () => { @@ -67,4 +72,125 @@ describe('EditorService', () => { .then(() => fail('length == 0 should throw exception')) .catch(err => expect(err).toEqual('could not load file')); }); + + it('editor state should be cached correctly', async () => { + const service: EditorService = TestBed.get(EditorService); + const filename = 'testname'; + + return new Promise(async resolve => { + expect(service.loadEditorState()).toEqual({ + editorValue: '', + filename: 'unnamed.yaml', + jobId: null, + queueId: null + }); + + service.setEditorValue(testData.jobYaml); + service.setFilename(filename); + service.setEditorJob(testData.jobOne); + service.setEditorQueue(testData.queueScheduled); + + const storedState = service.loadEditorState(); + + expect(storedState.editorValue).toEqual(testData.jobYaml); + expect(storedState.filename).toEqual(filename); + expect(storedState.jobId).toEqual(testData.jobOne.id); + expect(storedState.queueId).toEqual(testData.queueScheduled.id); + + resolve(); + }); + }); + + it('editor state should be restored correctly from cache', async () => { + const service: EditorService = TestBed.get(EditorService); + const filename = 'testname'; + + spyOn(service.jobDataService, 'findJob').and.callFake(jobId => { + expect(jobId).toEqual(testData.jobOne.id); + return of(testData.jobOne); + }); + + spyOn(service.queueDataService, 'findQueuedTask').and.callFake(queueId => { + expect(queueId).toEqual(testData.queueScheduled.id); + return of(testData.queueScheduled); + }); + + spyOn(service.jobDataService, 'jobGraph').and.returnValue(of({})); + spyOn(service.queueDataService, 'listenToQueueTaskStatus').and.stub(); + + return new Promise(async resolve => { + + const storedState = { + editorValue: testData.jobYaml, + filename: filename, + jobId: testData.jobOne.id, + queueId: testData.queueCompleted.id + }; + + await service.composeStorageState(storedState); + + await service.getEditorValue() + .pipe(take(1)) + .subscribe( + val => expect(val).toEqual(testData.jobYaml), + () => fail('getEditorValue should not fail')); + + await service.getFilename() + .pipe(take(1)) + .subscribe( + val => expect(val).toEqual(filename), + () => fail('getFilename should not fail')); + + + service.getEditorJob() + .pipe(take(1)) + .subscribe( + val => expect(val).toEqual(testData.jobOne), + () => fail('getEditorJob should not fail')); + + await service.getEditorQueue() + .pipe(take(1)) + .subscribe( + val => expect(val).toEqual(testData.queueScheduled), + () => fail('getEditorQueue should not fail') + ); + + expect(service.jobDataService.jobGraph).toHaveBeenCalledTimes(1); + expect(service.queueDataService.listenToQueueTaskStatus).toHaveBeenCalledTimes(1); + + resolve(); + }); + }); + + it('queue should not be restored if job isnt found', async () => { + const service: EditorService = TestBed.get(EditorService); + + spyOn(service.jobDataService, 'findJob').and.callFake(jobId => { + expect(jobId).toEqual(testData.jobOne.id); + return of(testData.jobOne); + }); + + spyOn(service.queueDataService, 'findQueuedTask').and.returnValue(of(null)); + + spyOn(service.jobDataService, 'jobGraph').and.returnValue(of({})); + spyOn(service.queueDataService, 'listenToQueueTaskStatus').and.stub(); + + return new Promise(async resolve => { + + const storedState = { + editorValue: '', + filename: '', + jobId: null, + queueId: null + }; + + await service.composeStorageState(storedState); + + expect(service.jobDataService.findJob).not.toHaveBeenCalled(); + expect(service.queueDataService.findQueuedTask).not.toHaveBeenCalled(); + expect(service.queueDataService.listenToQueueTaskStatus).not.toHaveBeenCalled(); + + resolve(); + }); + }); }); diff --git a/src/app/services/editor.service.ts b/src/app/services/editor.service.ts index 9481f7d..953c28e 100644 --- a/src/app/services/editor.service.ts +++ b/src/app/services/editor.service.ts @@ -17,7 +17,7 @@ import {EditorStorageState} from '../model/EditorStorageState'; }) export class EditorService { - private readonly EDITOR_STORE_KEY = 'editor'; + public readonly EDITOR_STORE_KEY = 'editor'; /** @ignore */ constructor(public store: Store, @@ -176,8 +176,8 @@ export class EditorService { storedState.filename )); // fetch job if existing - if (storedState.JobId) { - this.jobDataService.findJob(storedState.JobId) + if (storedState.jobId) { + this.jobDataService.findJob(storedState.jobId) .pipe(filter(v => v !== undefined), take(1)) .subscribe(job => { @@ -186,29 +186,30 @@ export class EditorService { this.jobDataService.jobGraph(job.id) .subscribe(graph => this.store.dispatch(new EditorActions.SetEditorGraphAction(graph))); + + // fetch queue if job was found and queue was found in cache + if (storedState.queueId) { + this.queueDataService.findQueuedTask(storedState.queueId) + .subscribe(queue => + this.setEditorQueue(queue) + ); + } } ); } - // fetch queue - if (storedState.queueId) { - this.queueDataService.findQueuedTask(storedState.queueId) - .subscribe(queue => - this.store.dispatch(new EditorActions.SetEditorQueueAction(queue)) - ); - } } - private saveEditorState(state: EditorStorageState) { + public saveEditorState(state: EditorStorageState) { localStorage.setItem(this.EDITOR_STORE_KEY, JSON.stringify(state)); } - private loadEditorState(): EditorStorageState { + public loadEditorState(): EditorStorageState { const parsed: EditorStorageState = JSON.parse(localStorage.getItem(this.EDITOR_STORE_KEY)); return parsed ? { editorValue: parsed.editorValue ? parsed.editorValue : '', filename: parsed.filename ? parsed.filename : 'unnamed.yaml', - JobId: parsed.JobId ? parsed.JobId : null, + jobId: parsed.jobId ? parsed.jobId : null, queueId: parsed.queueId ? parsed.queueId : null } : null; } diff --git a/src/app/services/queue-data.service.ts b/src/app/services/queue-data.service.ts index b270423..346bc36 100644 --- a/src/app/services/queue-data.service.ts +++ b/src/app/services/queue-data.service.ts @@ -16,6 +16,8 @@ import {ToastType} from '../app-dialog/model/toast'; }) export class QueueDataService { + private readonly queuePollingInterval = 1000; + /** * Subscription of the queueStatus interval. */ @@ -51,10 +53,11 @@ export class QueueDataService { const destroy = new Subject(); // poll Server every second this.queueStatusSubscription = - interval(1000).pipe( - takeUntil(destroy), - mergeMap(() => this.findQueuedTask(id)) - ).subscribe((queue: Queue) => { + interval(this.queuePollingInterval) + .pipe( + takeUntil(destroy), + mergeMap(() => this.findQueuedTask(id)) + ).subscribe((queue: Queue) => { this.store.dispatch(new EditorActions.SetEditorQueueAction(queue)); // if queue finished, stop polling if (queue.status === 'COMPLETED' || queue.status === 'FAILED') { From e0de3744bbc0eff8d4c70e24e61bbe9ca53e9ef5 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Tue, 13 Aug 2019 10:08:40 +0200 Subject: [PATCH 08/20] schedule improvements WiP --- .../schedules-bottom-sheet.component.html | 26 ++++++--- .../schedules-bottom-sheet.component.scss | 4 ++ .../schedules-overview.component.ts | 2 +- .../schedules-view.component.html | 12 +++-- .../schedules-view.component.scss | 10 ++++ .../schedules-view.component.ts | 53 +++++++++++++++++-- 6 files changed, 91 insertions(+), 16 deletions(-) diff --git a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html index 52e57cb..f6cc4de 100644 --- a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html +++ b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html @@ -25,14 +25,24 @@
-
    -
  • - process id: {{selected?.data.id}} -
  • -
  • - ip adress: {{selected?.data.ip}} -
  • -
+ + + + + + + + + + + +
process id:{{selected?.data.id}}
+
ip addresses:
+
+
+
{{ip}}
+
+
diff --git a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.scss b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.scss index b35b34c..e3eb358 100644 --- a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.scss +++ b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.scss @@ -1,5 +1,9 @@ @import "~bulma/sass/utilities/mixins"; +td { + border: none !important; +} + .bottom-sheet { position: absolute; padding: 1rem; diff --git a/src/app/components/schedules/schedules-overview/schedules-overview.component.ts b/src/app/components/schedules/schedules-overview/schedules-overview.component.ts index 4da1923..175a11b 100644 --- a/src/app/components/schedules/schedules-overview/schedules-overview.component.ts +++ b/src/app/components/schedules/schedules-overview/schedules-overview.component.ts @@ -3,7 +3,7 @@ import {ProcessDataService} from '../../../services/process-data.service'; import {merge, Observable, of, Subscription, zip} from 'rxjs'; import {ScheduleView} from '../../../model/ScheduleView'; import {JobDataService} from '../../../services/job-data.service'; -import {delay, flatMap, map, mergeAll, mergeMap, tap, zipAll} from 'rxjs/operators'; +import {delay, flatMap, map, mergeAll, mergeMap, repeat, repeatWhen, take, tap, zipAll} from 'rxjs/operators'; import {ActivatedRoute, Router} from '@angular/router'; import * as testData from 'testing/test-data'; import {flatten} from '@angular/compiler'; diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.html b/src/app/components/schedules/schedules-view/schedules-view.component.html index b3ec355..651cd3d 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.html +++ b/src/app/components/schedules/schedules-view/schedules-view.component.html @@ -1,15 +1,19 @@
-
+
- +
+ +
+ Nothing to show yet + (state: {{scheduleView?.schedule?.state}})
-
+
diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.scss b/src/app/components/schedules/schedules-view/schedules-view.component.scss index 5da6921..4208363 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.scss +++ b/src/app/components/schedules/schedules-view/schedules-view.component.scss @@ -24,6 +24,16 @@ align-items: center; } +.center-container { + position: absolute; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; +} + @include tablet { .view-header { padding: 0.6rem 0.8rem; diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.ts b/src/app/components/schedules/schedules-view/schedules-view.component.ts index 4e15557..c9ea61e 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.ts +++ b/src/app/components/schedules/schedules-view/schedules-view.component.ts @@ -7,6 +7,7 @@ import {ToastService} from '../../../app-dialog/services/toast.service'; import {ToastType} from '../../../app-dialog/model/toast'; import {Router} from '@angular/router'; import {DeleteScheduleDialogComponent} from '../../../app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component'; +import {CloudiatorProcess} from 'cloudiator-rest-api/model/cloudiatorProcess'; /** * View of a selected Schedule, containing a Cytoscape and a bottomsheet for further information @@ -72,6 +73,24 @@ export class SchedulesViewComponent implements OnInit, OnChanges { 'width': '5px', 'overlay-padding': '3px' } + }, { + 'selector': '.running', + 'style': { + 'background-color': 'hsl(141, 71%, 48%)', + 'text-outline-color': 'hsl(141, 71%, 48%)', + } + }, { + 'selector': '.pending', + 'style': { + 'background-color': 'hsl(48, 100%, 67%)', + 'text-outline-color': 'hsl(48, 100%, 67%)', + } + }, { + 'selector': '.error', + 'style': { + 'background-color': 'hsl(348, 100%, 61%)', + 'text-outline-color': 'hsl(348, 100%, 61%)', + } }]; /** @@ -100,6 +119,8 @@ export class SchedulesViewComponent implements OnInit, OnChanges { */ readonly minZoom = 0.5; + noData = true; + /** * Cytoscape object */ @@ -148,6 +169,7 @@ export class SchedulesViewComponent implements OnInit, OnChanges { /** @ignore */ ngOnChanges(changes: SimpleChanges) { + console.log(this.scheduleView) // make sure that graph is clear when no schedule view is selected, to prevent flickering on selection if (!this.scheduleView && this._cy) { this._cy.remove(this.cy.$(() => true)); @@ -179,11 +201,34 @@ export class SchedulesViewComponent implements OnInit, OnChanges { .subscribe(graph => { // timeout as hack to fix wrong positioning of graph when data arrives to early in mobile view setTimeout(() => { + this.noData = graph.nodes.length === 0; this.cy.remove(this.cy.$(() => true)); this.cy.add(graph); this.cy.layout(this.circLayout).run(); this.isLoading = false; this.cy.panBy({x: 0, y: -100}); + this.cy.elements('node').forEach(ele => { + const state: CloudiatorProcess.StateEnum = ele._private.data.state; + switch (state) { + case 'PENDING': + ele.addClass('pending'); + break; + case 'ERROR': + ele.addClass('error'); + break; + case 'DELETED': + ele.addClass('deleted'); + break; + case 'FINISHED': + ele.addClass('finished'); + break; + case 'RUNNING': + ele.addClass('running'); + break; + default: + break; + } + }); }, 0); }, () => { @@ -193,9 +238,11 @@ export class SchedulesViewComponent implements OnInit, OnChanges { } onDelete() { - this.dialogSerivce.open(DeleteScheduleDialogComponent, {data: { - scheduleName: this.scheduleView.job.name - }}) + this.dialogSerivce.open(DeleteScheduleDialogComponent, { + data: { + scheduleName: this.scheduleView.job.name + } + }) .afterClosed() .subscribe(confirmation => { if (confirmation) { From c3575f1d3d0f36a25e6ab33b551da98aa18d18d6 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Tue, 13 Aug 2019 13:05:01 +0200 Subject: [PATCH 09/20] WiP implemented diagnostic view and design improvements --- package.json | 2 +- src/app/app-dialog/app-dialog.module.ts | 7 +++- .../schedule-diagnostic-dialog.component.html | 15 +++++++ .../schedule-diagnostic-dialog.component.scss | 0 ...hedule-diagnostic-dialog.component.spec.ts | 25 ++++++++++++ .../schedule-diagnostic-dialog.component.ts | 23 +++++++++++ .../schedules-bottom-sheet.component.html | 39 +++++++++++-------- .../schedules-bottom-sheet.component.ts | 12 +++++- .../schedules-view.component.ts | 11 +++++- 9 files changed, 112 insertions(+), 22 deletions(-) create mode 100644 src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.html create mode 100644 src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.scss create mode 100644 src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts create mode 100644 src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.ts diff --git a/package.json b/package.json index aa6be98..4c85bdd 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,6 @@ "protractor": "^5.4.2", "ts-node": "^7.0.1", "tslint": "^5.18.0", - "typescript": "<3.5.0" + "typescript": " >=3.4.0 <3.6.0" } } diff --git a/src/app/app-dialog/app-dialog.module.ts b/src/app/app-dialog/app-dialog.module.ts index 428bf5c..cc47944 100644 --- a/src/app/app-dialog/app-dialog.module.ts +++ b/src/app/app-dialog/app-dialog.module.ts @@ -6,6 +6,7 @@ import {ToastService} from './services/toast.service'; import {ConfirmNewCloudDialogComponent} from './dialogs/confirm-new-cloud-dialog/confirm-new-cloud-dialog.component'; import {DeleteCloudDialogComponent} from './dialogs/delete-cloud-dialog/delete-cloud-dialog.component'; import { DeleteScheduleDialogComponent } from './dialogs/delete-schedule-dialog/delete-schedule-dialog.component'; +import { ScheduleDiagnosticDialogComponent } from './dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component'; /** * Main Module handling App DIalogs and Toasts. @@ -15,7 +16,8 @@ import { DeleteScheduleDialogComponent } from './dialogs/delete-schedule-dialog/ ToastComponent, ConfirmNewCloudDialogComponent, DeleteCloudDialogComponent, - DeleteScheduleDialogComponent + DeleteScheduleDialogComponent, + ScheduleDiagnosticDialogComponent ], imports: [ OverlayModule @@ -28,7 +30,8 @@ import { DeleteScheduleDialogComponent } from './dialogs/delete-schedule-dialog/ ToastComponent, ConfirmNewCloudDialogComponent, DeleteCloudDialogComponent, - DeleteScheduleDialogComponent + DeleteScheduleDialogComponent, + ScheduleDiagnosticDialogComponent ] }) export class AppDialogModule { diff --git a/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.html b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.html new file mode 100644 index 0000000..0b08314 --- /dev/null +++ b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.html @@ -0,0 +1,15 @@ + + diff --git a/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.scss b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts new file mode 100644 index 0000000..57a674c --- /dev/null +++ b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ScheduleDiagnosticDialogComponent } from './schedule-diagnostic-dialog.component'; + +describe('ScheduleDiagnosticDialogComponent', () => { + let component: ScheduleDiagnosticDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ScheduleDiagnosticDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ScheduleDiagnosticDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.ts b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.ts new file mode 100644 index 0000000..0bbedb7 --- /dev/null +++ b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.ts @@ -0,0 +1,23 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {DialogRef} from '../../model/dialogRef'; +import {DIALOG_DATA} from '../../services/dialog.service'; +import {CloudiatorProcess} from 'cloudiator-rest-api'; + +@Component({ + selector: 'app-schedule-diagnostic-dialog', + templateUrl: './schedule-diagnostic-dialog.component.html', + styleUrls: ['./schedule-diagnostic-dialog.component.scss'] +}) +export class ScheduleDiagnosticDialogComponent implements OnInit { + + constructor(public dialogRef: DialogRef, + @Inject(DIALOG_DATA) public data: CloudiatorProcess) { } + + ngOnInit() { + } + + onClose() { + this.dialogRef.close(); + } + +} diff --git a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html index f6cc4de..a9db678 100644 --- a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html +++ b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.html @@ -18,13 +18,24 @@
+
{{selected?.data.state.toLowerCase()}} - - - - + + + + + + + + + + + + +
+ @@ -42,19 +53,15 @@ + + + +
process id:
+ process type: + + {{selected?.process?.type}} +
- - -
- - edge - -
-
- source: {{selected.data.source.task}}
- target: {{selected.data.target.task}} -
-
diff --git a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts index e24dff2..04a564f 100644 --- a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts +++ b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts @@ -1,5 +1,8 @@ import {Component, Input, OnInit} from '@angular/core'; import {animate, animateChild, group, query, state, style, transition, trigger} from '@angular/animations'; +import {CloudiatorProcess} from 'cloudiator-rest-api'; +import {DialogService} from '../../../app-dialog/services/dialog.service'; +import {ScheduleDiagnosticDialogComponent} from '../../../app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component'; /** * Bottomsheet component of the ScheduleView. @@ -95,6 +98,7 @@ export class SchedulesBottomSheetComponent implements OnInit { * @param {Selected} value */ @Input() set selected(value: Selected) { + console.log(value); this._selected = value; // open/hide bottomsheet depending if something is selected if (value) { @@ -111,7 +115,7 @@ export class SchedulesBottomSheetComponent implements OnInit { sheetOpenClose: 'open' | 'closed' | 'hidden' = this.selected ? 'open' : 'hidden'; /** @ignore */ - constructor() { + constructor(private dialogService: DialogService) { } /** @ignore */ @@ -129,6 +133,10 @@ export class SchedulesBottomSheetComponent implements OnInit { onSheetClick() { } + openDiagnostic() { + this.dialogService.open(ScheduleDiagnosticDialogComponent, {data: this.selected.process}); + } + } /** @@ -140,6 +148,8 @@ export interface Selected { */ data: any; + process: CloudiatorProcess; + /** * type of Object */ diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.ts b/src/app/components/schedules/schedules-view/schedules-view.component.ts index c9ea61e..023127c 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.ts +++ b/src/app/components/schedules/schedules-view/schedules-view.component.ts @@ -142,9 +142,16 @@ export class SchedulesViewComponent implements OnInit, OnChanges { this.cy.panBy({x: 0, y: -100}); }); this.cy.on('select', 'node', event => { + const data = event.target._private.data; + // find corresponding process of data + const process = this.scheduleView.schedule.processes ? + this.scheduleView.schedule.processes.find(p => p.id === data.id) + : null; + this.selected = { group: 'nodes', - data: event.target._private.data + process: process, + data: data }; }); this.cy.on('unselect', () => { @@ -169,7 +176,7 @@ export class SchedulesViewComponent implements OnInit, OnChanges { /** @ignore */ ngOnChanges(changes: SimpleChanges) { - console.log(this.scheduleView) + console.log(this.scheduleView); // make sure that graph is clear when no schedule view is selected, to prevent flickering on selection if (!this.scheduleView && this._cy) { this._cy.remove(this.cy.$(() => true)); From 70eac559c188796c499a172ce7f57303b435cf4d Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Mon, 2 Sep 2019 08:33:04 +0200 Subject: [PATCH 10/20] fixed basic tests, misc. changes --- src/app/app-dialog/app-dialog.module.ts | 12 +++++++----- src/app/app-dialog/dialogs/index.ts | 13 +++++++++++++ ...chedule-diagnostic-dialog.component.spec.ts | 18 ++++++++++++++---- .../hardware-overview.component.ts | 6 ++---- .../images-overview.component.ts | 4 ++-- .../schedules-bottom-sheet.component.spec.ts | 4 +++- .../schedules-bottom-sheet.component.ts | 2 +- src/app/root-store/cloud-data-store/reducer.ts | 2 +- 8 files changed, 43 insertions(+), 18 deletions(-) create mode 100644 src/app/app-dialog/dialogs/index.ts diff --git a/src/app/app-dialog/app-dialog.module.ts b/src/app/app-dialog/app-dialog.module.ts index cc47944..57a7997 100644 --- a/src/app/app-dialog/app-dialog.module.ts +++ b/src/app/app-dialog/app-dialog.module.ts @@ -1,12 +1,14 @@ import {NgModule} from '@angular/core'; import {OverlayModule} from '@angular/cdk/overlay'; -import {ToastComponent} from './dialogs/toast/toast.component'; +import { + ToastComponent, + ConfirmNewCloudDialogComponent, + DeleteCloudDialogComponent, + DeleteScheduleDialogComponent, + ScheduleDiagnosticDialogComponent +} from './dialogs'; import {DialogService} from './services/dialog.service'; import {ToastService} from './services/toast.service'; -import {ConfirmNewCloudDialogComponent} from './dialogs/confirm-new-cloud-dialog/confirm-new-cloud-dialog.component'; -import {DeleteCloudDialogComponent} from './dialogs/delete-cloud-dialog/delete-cloud-dialog.component'; -import { DeleteScheduleDialogComponent } from './dialogs/delete-schedule-dialog/delete-schedule-dialog.component'; -import { ScheduleDiagnosticDialogComponent } from './dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component'; /** * Main Module handling App DIalogs and Toasts. diff --git a/src/app/app-dialog/dialogs/index.ts b/src/app/app-dialog/dialogs/index.ts new file mode 100644 index 0000000..162c14b --- /dev/null +++ b/src/app/app-dialog/dialogs/index.ts @@ -0,0 +1,13 @@ +import {ConfirmNewCloudDialogComponent} from './confirm-new-cloud-dialog/confirm-new-cloud-dialog.component'; +import {DeleteCloudDialogComponent} from './delete-cloud-dialog/delete-cloud-dialog.component'; +import {DeleteScheduleDialogComponent} from './delete-schedule-dialog/delete-schedule-dialog.component'; +import {ScheduleDiagnosticDialogComponent} from './schedule-diagnostic-dialog/schedule-diagnostic-dialog.component'; +import {ToastComponent} from './toast/toast.component'; + +export { + ConfirmNewCloudDialogComponent, + DeleteScheduleDialogComponent, + DeleteCloudDialogComponent, + ScheduleDiagnosticDialogComponent, + ToastComponent +}; diff --git a/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts index 57a674c..f5895e8 100644 --- a/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts +++ b/src/app/app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component.spec.ts @@ -1,6 +1,9 @@ -import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import {async, ComponentFixture, TestBed} from '@angular/core/testing'; -import { ScheduleDiagnosticDialogComponent } from './schedule-diagnostic-dialog.component'; +import {ScheduleDiagnosticDialogComponent} from './schedule-diagnostic-dialog.component'; +import {AppDialogModule} from '../../app-dialog.module'; +import {DialogRef} from '../../model/dialogRef'; +import {DIALOG_DATA} from '../../services/dialog.service'; describe('ScheduleDiagnosticDialogComponent', () => { let component: ScheduleDiagnosticDialogComponent; @@ -8,9 +11,16 @@ describe('ScheduleDiagnosticDialogComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ ScheduleDiagnosticDialogComponent ] + declarations: [ + ScheduleDiagnosticDialogComponent + ], + imports: [], + providers: [ + {provide: DialogRef, useVale: {}}, + { provide: DIALOG_DATA, useValue: {}} + ] }) - .compileComponents(); + .compileComponents(); })); beforeEach(() => { diff --git a/src/app/components/overview-tables/hardware-overview/hardware-overview.component.ts b/src/app/components/overview-tables/hardware-overview/hardware-overview.component.ts index 19a1310..5d5a429 100644 --- a/src/app/components/overview-tables/hardware-overview/hardware-overview.component.ts +++ b/src/app/components/overview-tables/hardware-overview/hardware-overview.component.ts @@ -1,11 +1,8 @@ import {Component, OnDestroy, OnInit} from '@angular/core'; -import {BehaviorSubject, combineLatest, Subscription} from 'rxjs'; import {Hardware} from 'cloudiator-rest-api'; import {CloudDataService} from '../../../services/cloud-data.service'; -import {FormControl} from '@angular/forms'; import {ActivatedRoute} from '@angular/router'; import {OverviewTableComponent} from '../overview-table.component'; -import {map} from 'rxjs/operators'; /** * Overview Component showing available Hardware. @@ -24,7 +21,8 @@ export class HardwareOverviewComponent extends OverviewTableComponent name: {value: 'Name'}, cores: {value: 'Cores'}, ram: {value: 'Ram'}, - disk: {value: 'Disk'} + disk: {value: 'Disk'}, + providerId: {value: 'ProviderId'} }; this.initialSortKey = 'cores'; this.isLoading$ = cloudDataService.hardwareIsLoading(); diff --git a/src/app/components/overview-tables/images-overview/images-overview.component.ts b/src/app/components/overview-tables/images-overview/images-overview.component.ts index 33aa9fd..4d992aa 100644 --- a/src/app/components/overview-tables/images-overview/images-overview.component.ts +++ b/src/app/components/overview-tables/images-overview/images-overview.component.ts @@ -18,12 +18,12 @@ export class ImagesOverviewComponent extends OverviewTableComponent imple super(activatedRoute, cloudDataService); this.title = 'Images'; this.columns = { - providerId: {value: 'provider ID'}, name: {value: 'Name'}, os: { value: 'OS', selectionFn: (v: Image) => v.operatingSystem ? v.operatingSystem.operatingSystemFamily : '' - } + }, + providerId: {value: 'provider ID'} }; this.initialSortKey = ''; this.isLoading$ = cloudDataService.imageIsLoading(); diff --git a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.spec.ts b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.spec.ts index 85ff70c..258cba1 100644 --- a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.spec.ts +++ b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.spec.ts @@ -2,6 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SchedulesBottomSheetComponent } from './schedules-bottom-sheet.component'; import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; +import {AppDialogModule} from '../../../app-dialog/app-dialog.module'; describe('SchedulesBottomSheetComponent', () => { let component: SchedulesBottomSheetComponent; @@ -11,7 +12,8 @@ describe('SchedulesBottomSheetComponent', () => { TestBed.configureTestingModule({ declarations: [ SchedulesBottomSheetComponent ], imports: [ - BrowserAnimationsModule + BrowserAnimationsModule, + AppDialogModule ] }) .compileComponents(); diff --git a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts index 04a564f..00c7125 100644 --- a/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts +++ b/src/app/components/schedules/schedules-bottom-sheet/schedules-bottom-sheet.component.ts @@ -2,7 +2,7 @@ import {Component, Input, OnInit} from '@angular/core'; import {animate, animateChild, group, query, state, style, transition, trigger} from '@angular/animations'; import {CloudiatorProcess} from 'cloudiator-rest-api'; import {DialogService} from '../../../app-dialog/services/dialog.service'; -import {ScheduleDiagnosticDialogComponent} from '../../../app-dialog/dialogs/schedule-diagnostic-dialog/schedule-diagnostic-dialog.component'; +import {ScheduleDiagnosticDialogComponent} from '../../../app-dialog/dialogs'; /** * Bottomsheet component of the ScheduleView. diff --git a/src/app/root-store/cloud-data-store/reducer.ts b/src/app/root-store/cloud-data-store/reducer.ts index 8553120..55c5be9 100644 --- a/src/app/root-store/cloud-data-store/reducer.ts +++ b/src/app/root-store/cloud-data-store/reducer.ts @@ -44,6 +44,6 @@ export function cloudDataReducer(state = initialState, action: cloudActions.All) locationIsLoading: action.isLoading }; default: - return initialState; + return state; } } From 76dbc2e2e09ee886dd14d6110f4b5f9ae91313f5 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Mon, 2 Sep 2019 14:11:04 +0200 Subject: [PATCH 11/20] implemented mobile view for editor --- src/app/app-routing.module.ts | 3 +- src/app/components/app/app.component.html | 1 + src/app/components/app/app.component.ts | 6 +++- .../editor-graph-view.component.html | 22 ++++++++++---- .../editor-graph-view.component.scss | 22 +++++++++++++- .../editor-graph-view.component.ts | 11 +++++-- .../yaml-editor/yaml-editor.component.html | 22 ++++++++++---- .../yaml-editor/yaml-editor.component.scss | 10 +++++++ .../yaml-editor/yaml-editor.component.spec.ts | 4 ++- .../yaml-editor/yaml-editor.component.ts | 30 +++++++++++++++---- .../yaml-graph/yaml-graph.component.html | 7 ++++- .../yaml-graph/yaml-graph.component.scss | 9 ++++++ .../editor/yaml-graph/yaml-graph.component.ts | 21 +++++++++---- src/app/services/auth.service.ts | 4 +++ src/styles.scss | 6 ++++ 15 files changed, 148 insertions(+), 30 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index c9a5163..8c62789 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -26,7 +26,8 @@ const routes: Routes = [ {path: 'locations', component: LocationsOverviewComponent, canActivate: [AuthGuard]}, - {path: 'editor', component: YamlEditorComponent, canActivate: [AuthGuard]}, + {path: 'editor', component: YamlEditorComponent, data: {graphs: false}, canActivate: [AuthGuard]}, + {path: 'editor/graphs', component: YamlEditorComponent, data: {graphs: true}, canActivate: [AuthGuard]}, {path: 'schedules', component: SchedulesOverviewComponent}, diff --git a/src/app/components/app/app.component.html b/src/app/components/app/app.component.html index 9b11a46..c6710a7 100644 --- a/src/app/components/app/app.component.html +++ b/src/app/components/app/app.component.html @@ -44,6 +44,7 @@ - Graphs diff --git a/src/app/services/editor.service.ts b/src/app/services/editor.service.ts index 4429790..53d9906 100644 --- a/src/app/services/editor.service.ts +++ b/src/app/services/editor.service.ts @@ -96,6 +96,8 @@ export class EditorService { setEditorQueue(queue: Queue) { if (queue) { this.queueDataService.listenToQueueTaskStatus(queue.id); + } else { + this.queueDataService.listenToQueueTaskStatus(null); } this.store.dispatch(new EditorActions.SetEditorQueueAction(queue)); } diff --git a/src/app/services/queue-data.service.ts b/src/app/services/queue-data.service.ts index 346bc36..75c9876 100644 --- a/src/app/services/queue-data.service.ts +++ b/src/app/services/queue-data.service.ts @@ -50,6 +50,11 @@ export class QueueDataService { this.queueStatusSubscription.unsubscribe(); } + // cancel if id is empty + if (!id) { + return; + } + const destroy = new Subject(); // poll Server every second this.queueStatusSubscription = From 174f453279781c4b6bf78ae57d3a1844d5111398 Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Sat, 14 Sep 2019 10:20:18 +0200 Subject: [PATCH 13/20] minor improvements --- .gitignore | 2 +- package.json | 1 + .../editor-graph-view.component.html | 2 +- .../editor/yaml-graph/yaml-graph.component.ts | 12 +- .../schedules-view.component.ts | 104 +++++++++--------- 5 files changed, 63 insertions(+), 58 deletions(-) diff --git a/.gitignore b/.gitignore index 0313a28..2d84294 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ # IDE - VSCode .vscode/* -!.vscode/settings.json +.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json diff --git a/package.json b/package.json index 4c85bdd..0ad1721 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "@angular/compiler-cli": "^8.1.1", "@angular/language-service": "^8.1.1", "@compodoc/compodoc": "^1.1.10", + "@types/cytoscape": "^3.8.1", "@types/jasmine": "^2.8.16", "@types/jasminewd2": "^2.0.6", "@types/node": "^10.14.12", diff --git a/src/app/components/editor/editor-graph-view/editor-graph-view.component.html b/src/app/components/editor/editor-graph-view/editor-graph-view.component.html index 332fb10..064cb4c 100644 --- a/src/app/components/editor/editor-graph-view/editor-graph-view.component.html +++ b/src/app/components/editor/editor-graph-view/editor-graph-view.component.html @@ -17,7 +17,7 @@ [@tabEnterAnimation] style="overflow-x: hidden; flex-basis: 0"> - Nodes + Schedule diff --git a/src/app/components/editor/yaml-graph/yaml-graph.component.ts b/src/app/components/editor/yaml-graph/yaml-graph.component.ts index 5a722f1..f94f9dd 100644 --- a/src/app/components/editor/yaml-graph/yaml-graph.component.ts +++ b/src/app/components/editor/yaml-graph/yaml-graph.component.ts @@ -17,7 +17,7 @@ export class YamlGraphComponent implements OnInit { /** * Graph Style. */ - readonly style = [{ + readonly style: any = [{ 'selector': 'node', 'style': { 'width': '50%', @@ -35,15 +35,13 @@ export class YamlGraphComponent implements OnInit { }, { 'selector': 'edge', 'style': { + 'curve-style': 'bezier', 'opacity': '0.9', 'line-color': '#92acbe', 'width': '5px', - 'overlay-padding': '3px' - } - }, { - 'selector': 'edge.unhighlighted', - 'style': { - 'opacity': '0.05' + 'overlay-opacity': '0', + 'source-arrow-shape': 'triangle-backcurve', + 'source-arrow-color': '#92acbe' } }]; diff --git a/src/app/components/schedules/schedules-view/schedules-view.component.ts b/src/app/components/schedules/schedules-view/schedules-view.component.ts index 023127c..6486f8c 100644 --- a/src/app/components/schedules/schedules-view/schedules-view.component.ts +++ b/src/app/components/schedules/schedules-view/schedules-view.component.ts @@ -8,6 +8,8 @@ import {ToastType} from '../../../app-dialog/model/toast'; import {Router} from '@angular/router'; import {DeleteScheduleDialogComponent} from '../../../app-dialog/dialogs/delete-schedule-dialog/delete-schedule-dialog.component'; import {CloudiatorProcess} from 'cloudiator-rest-api/model/cloudiatorProcess'; +import {Stylesheet} from 'cytoscape'; +import Edge = cytoscape.Css.Edge; /** * View of a selected Schedule, containing a Cytoscape and a bottomsheet for further information @@ -43,55 +45,59 @@ export class SchedulesViewComponent implements OnInit, OnChanges { /** * Style of graph. */ - readonly style = [{ - 'selector': 'node', - 'style': { - 'width': '50%', - 'height': '50%', - 'content': 'data(task)', - 'font-size': '12px', - 'text-valign': 'center', - 'text-halign': 'center', - 'background-color': '#00447a', - 'text-outline-color': '#00447a', - 'text-outline-width': '1px', - 'text-wrap': 'wrap', - 'color': '#fff', - 'z-index': '10' - } - }, { - 'selector': 'node:selected', - 'style': { - 'background-color': 'lightgray', - 'text-outline-color': 'gray' - } - }, { - 'selector': 'edge', - 'style': { - 'opacity': '0.9', - 'line-color': '#92acbe', - 'width': '5px', - 'overlay-padding': '3px' - } - }, { - 'selector': '.running', - 'style': { - 'background-color': 'hsl(141, 71%, 48%)', - 'text-outline-color': 'hsl(141, 71%, 48%)', - } - }, { - 'selector': '.pending', - 'style': { - 'background-color': 'hsl(48, 100%, 67%)', - 'text-outline-color': 'hsl(48, 100%, 67%)', - } - }, { - 'selector': '.error', - 'style': { - 'background-color': 'hsl(348, 100%, 61%)', - 'text-outline-color': 'hsl(348, 100%, 61%)', - } - }]; + readonly style: any = [ + { + 'selector': 'node', + 'style': { + 'shape': 'ellipse', + 'width': '50', + 'height': '50', + 'content': 'data(task)', + 'font-size': '12px', + 'text-valign': 'center', + 'text-halign': 'center', + 'background-color': '#00447a', + 'text-outline-color': '#00447a', + 'text-outline-width': '1px', + 'text-wrap': 'wrap', + 'color': '#fff', + 'z-index': '10' + } + }, { + 'selector': 'node:selected', + 'style': { + 'background-blacken': '-0.2' + } + }, { + 'selector': 'edge', + 'style': { + 'curve-style': 'bezier', + 'opacity': '0.9', + 'line-color': '#92acbe', + 'width': '5px', + 'overlay-opacity': '0', + 'source-arrow-shape': 'triangle-backcurve', + 'source-arrow-color': '#92acbe' + } + }, { + 'selector': '.running', + 'style': { + 'background-color': 'hsl(141, 71%, 48%)', + 'text-outline-color': 'hsl(141, 71%, 48%)', + } + }, { + 'selector': '.pending', + 'style': { + 'background-color': 'hsl(48, 100%, 67%)', + 'text-outline-color': 'hsl(48, 100%, 67%)', + } + }, { + 'selector': '.error', + 'style': { + 'background-color': 'hsl(348, 100%, 61%)', + 'text-outline-color': 'hsl(348, 100%, 61%)' + } + }]; /** * Graph Sorter. From 33f78e0ad6597dd454b628f50dd60ed4e86991ca Mon Sep 17 00:00:00 2001 From: Florian Lappe Date: Sat, 14 Sep 2019 18:24:57 +0200 Subject: [PATCH 14/20] improved cloud view design --- src/app/components/app/app.component.css | 4 - src/app/components/app/app.component.html | 40 +-- src/app/components/app/app.component.scss | 13 + src/app/components/app/app.component.ts | 2 +- .../cloud-card/cloud-card.component.html | 25 +- .../cloud-card/cloud-card.component.scss | 66 +++++ .../clouds/cloud-card/cloud-card.component.ts | 14 ++ .../cloud-overview.component.html | 15 +- .../cloud-overview.component.scss | 12 + .../cloud-view/cloud-view.component.html | 227 ++++++++++++------ .../cloud-view/cloud-view.component.scss | 49 ++++ .../clouds/cloud-view/cloud-view.component.ts | 44 +++- .../clouds/new-cloud/new-cloud.component.html | 19 +- src/app/services/cloud-data.service.ts | 20 +- src/assets/AWS-Logo.svg | 38 +++ src/assets/GCP-Logo.svg | 1 + src/assets/OpenStack-Logo.svg | 1 + src/assets/default-cloud-logo.svg | 1 + src/styles.scss | 7 +- 19 files changed, 457 insertions(+), 141 deletions(-) delete mode 100644 src/app/components/app/app.component.css create mode 100644 src/app/components/app/app.component.scss create mode 100644 src/assets/AWS-Logo.svg create mode 100644 src/assets/GCP-Logo.svg create mode 100644 src/assets/OpenStack-Logo.svg create mode 100644 src/assets/default-cloud-logo.svg diff --git a/src/app/components/app/app.component.css b/src/app/components/app/app.component.css deleted file mode 100644 index d8b4971..0000000 --- a/src/app/components/app/app.component.css +++ /dev/null @@ -1,4 +0,0 @@ -.no-nav-app-container { - height: 100%; - top: 0; -} diff --git a/src/app/components/app/app.component.html b/src/app/components/app/app.component.html index c6710a7..b383ef8 100644 --- a/src/app/components/app/app.component.html +++ b/src/app/components/app/app.component.html @@ -3,7 +3,7 @@