diff --git a/ngapp/src/app/dataservices/dataservices.module.ts b/ngapp/src/app/dataservices/dataservices.module.ts index ffde9b36..875abbaf 100644 --- a/ngapp/src/app/dataservices/dataservices.module.ts +++ b/ngapp/src/app/dataservices/dataservices.module.ts @@ -77,6 +77,7 @@ import { CreateViewsDialogComponent } from './create-views-dialog/create-views-d import { SetDescriptionDialogComponent } from "@dataservices/set-description-dialog/set-description-dialog.component"; import { PropertyEditorComponent } from './virtualization/view-editor/view-property-editors/property-editor/property-editor.component'; import { ProjectedColumnsEditorComponent } from './virtualization/view-editor/view-property-editors/projected-columns-editor/projected-columns-editor.component'; +import { ViewsListComponent} from './virtualization/view-editor/views-list/views-list.component'; @NgModule({ imports: [ @@ -133,7 +134,8 @@ import { ProjectedColumnsEditorComponent } from './virtualization/view-editor/vi CreateViewsDialogComponent, SetDescriptionDialogComponent, PropertyEditorComponent, - ProjectedColumnsEditorComponent + ProjectedColumnsEditorComponent, + ViewsListComponent ], providers: [ { diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/canvas.service.ts b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/canvas.service.ts index b13c550f..ef0addca 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/canvas.service.ts +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/canvas.service.ts @@ -171,6 +171,8 @@ export class CanvasService { } public clear(): void { + if (this.canvasGraph == null) + return; this.canvasGraph.clear(); } diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.css b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.css index 50bca7a7..b521ac0d 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.css +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.css @@ -4,8 +4,8 @@ #view-editor-canvas-editor { display: grid; grid-template-areas: - "canvas-editor properties-editor"; - grid-template-columns: 50fr 50fr; + "views-list-panel canvas-editor properties-editor"; + grid-template-columns: 20fr 50fr 30fr; height: 100%; } @@ -17,6 +17,13 @@ grid-area: canvas-editor; } +/* + * Views List Panel + */ +#views-list-container { + grid-area: views-list-panel; +} + /* * Configures the alert when view has no sources. */ diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.html b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.html index e7d6709b..555f09bf 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.html +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.html @@ -1,5 +1,10 @@
+
+ +
+ diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.spec.ts b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.spec.ts index 7ab84fb1..ebb1dee8 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.spec.ts +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/view-canvas.component.spec.ts @@ -21,12 +21,15 @@ import { VdbService } from "@dataservices/shared/vdb.service"; import { MockVdbService } from "@dataservices/shared/mock-vdb.service"; import { NotifierService } from "@dataservices/shared/notifier.service"; import { ViewPropertyEditorsComponent } from "@dataservices/virtualization/view-editor/view-property-editors/view-property-editors.component"; -import { TabsModule } from "ngx-bootstrap"; +import { TabsModule} from "ngx-bootstrap"; import { GraphVisualComponent, LinkVisualComponent, NodeVisualComponent } from "@dataservices/virtualization/view-editor/view-canvas/visuals"; import { CanvasService } from "@dataservices/virtualization/view-editor/view-canvas/canvas.service"; import { SelectionService } from "@core/selection.service"; import { PropertyEditorComponent } from "@dataservices/virtualization/view-editor/view-property-editors/property-editor/property-editor.component"; import { ProjectedColumnsEditorComponent } from "@dataservices/virtualization/view-editor/view-property-editors/projected-columns-editor/projected-columns-editor.component"; +import { ViewsListComponent } from "@dataservices/virtualization/view-editor/views-list/views-list.component"; +import { BsModalService } from "ngx-bootstrap"; +import { Dataservice } from "@dataservices/shared/dataservice.model"; describe('ViewCanvasComponent', () => { let component: ViewCanvasComponent; @@ -54,9 +57,11 @@ describe('ViewCanvasComponent', () => { ProjectedColumnsEditorComponent, PropertyEditorComponent, ViewCanvasComponent, - ViewPropertyEditorsComponent + ViewPropertyEditorsComponent, + ViewsListComponent ], providers: [ + BsModalService, { provide: AppSettingsService, useClass: MockAppSettingsService }, { provide: DataserviceService, useClass: MockDataserviceService }, CanvasService, @@ -75,6 +80,14 @@ describe('ViewCanvasComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(ViewCanvasComponent); component = fixture.componentInstance; + + const selService = TestBed.get( SelectionService ); + const ds: Dataservice = new Dataservice(); + ds.setId("testDs"); + ds.setServiceVdbName("testDsVdb"); + // noinspection JSUnusedAssignment + selService.setSelectedVirtualization( ds ); + fixture.detectChanges(); }); diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/visuals/node/node-visual.component.ts b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/visuals/node/node-visual.component.ts index 667d8495..68a7cb7f 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/visuals/node/node-visual.component.ts +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-canvas/visuals/node/node-visual.component.ts @@ -77,7 +77,7 @@ export class NodeVisualComponent { public get icon(): string { if (this.node.type === CanvasConstants.SOURCE_TYPE) - return "/assets/graphicsfuel/database-64.png"; + return "/assets/table.png"; else if (this.node.type === CanvasConstants.COMPOSITION_TYPE) return "/assets/composition.png"; diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.css b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.css index 828d08cd..9b87ebfc 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.css +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.css @@ -1,60 +1,3 @@ -/* - * The view description textarea. - */ -#view-editor-header-description-input { - max-width: max-content; - min-height: 20px; - min-width: 400px; - vertical-align: top; -} - -.list-pf-container { - -ms-flex-align: start; - align-items: flex-start; - display: -ms-flexbox; - display: flex; - padding: 0; -} - -.view-table-div { - padding-left: 0; - padding-right: 0; - margin-bottom: 5px; - min-height: 70px; - max-height: 70px; - border: 1px inset grey; - overflow-y: auto; -} - -.view-editor-header-name-input { - padding-left: 0; - padding-right: 5px; -} - -.view-editor-header-create-button { - margin-left: 0; - padding-left: 0; - padding-top: 8px; -} - -.view-editor-header-delete-button { - padding-left: 0; - padding-top: 8px; -} - -.view-editor-header-description-area { - padding-left: 0; - margin-left: 0; -} - -/* - * A div containing an input field in the header. - */ -.view-editor-header-input-div { - margin: 1px; - padding: 1px; -} - /* * A type for the header title */ @@ -62,49 +5,3 @@ margin-left: 10px; } -/* - * A type for vertically aligned labels in the header. - */ -.view-editor-header-label { - margin: 2px; - padding: 2px; -} - -/* - * The checkbox that shows/hides the virtualization description. - */ -#view-editor-header-show-description { - margin-right: 4px; -} - -/* - * The label for the checkbox that shows/hides the view description. - */ -#view-editor-header-show-description-label { - margin-top: 2px; - text-align: left; -} - -/* - * The label where the virtualization name is the content. - */ -#view-editor-header-virtualization-name { - text-align: left; -} - -/* - * Style the empty state component so that it is centered and extends the entire width. - */ -#view-editor-header-table .blank-slate-pf { - background-color: inherit; - min-width: 200px; - border: none; - padding: 0; -} - -/* - * Style text in blank slate for table - */ -#view-editor-header-table h1, .h1 { - font-size: 14px; -} diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.html b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.html index bd386954..6167371c 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.html +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.html @@ -4,57 +4,7 @@
-

Virtualization Name: '{{virtualizationName}}'

-
- - - - -
-
- Views: -
-
- - -
- -
-
-
- -
-
- -
-
- -
-
- - - - -
- {{viewDescriptionLabel}} - -
+

Virtualization Name: {{virtualizationName}}

diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.ts b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.ts index 0a733e60..bb46a189 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.ts +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor-header/view-editor-header.component.ts @@ -16,25 +16,7 @@ */ import { Component, OnDestroy, OnInit, ViewEncapsulation } from "@angular/core"; -import { LoggerService } from "@core/logger.service"; -import { ViewEditorPart } from "@dataservices/virtualization/view-editor/view-editor-part.enum"; import { ViewEditorService } from "@dataservices/virtualization/view-editor/view-editor.service"; -import { ViewEditorEvent } from "@dataservices/virtualization/view-editor/event/view-editor-event"; -import { ViewEditorI18n } from "@dataservices/virtualization/view-editor/view-editor-i18n"; -import { CommandFactory } from "@dataservices/virtualization/view-editor/command/command-factory"; -import { Subscription } from "rxjs/Subscription"; -import { Command } from "@dataservices/virtualization/view-editor/command/command"; -import { EmptyStateConfig, NgxDataTableConfig, TableConfig } from "patternfly-ng"; -import { ViewDefinition } from "@dataservices/shared/view-definition.model"; -import { BsModalService } from "ngx-bootstrap"; -import { CreateViewDialogComponent } from "@dataservices/virtualization/view-editor/create-view-dialog/create-view-dialog.component"; -import { DataserviceService } from "@dataservices/shared/dataservice.service"; -import { LoadingState } from "@shared/loading-state.enum"; -import { ConfirmDialogComponent } from "@shared/confirm-dialog/confirm-dialog.component"; -import { SelectionService } from "@core/selection.service"; -import { ViewEditorState } from "@dataservices/shared/view-editor-state.model"; -import { Dataservice } from "@dataservices/shared/dataservice.model"; -import { ViewEditorProgressChangeId } from "@dataservices/virtualization/view-editor/event/view-editor-save-progress-change-id.enum"; @Component({ encapsulation: ViewEncapsulation.None, @@ -44,151 +26,23 @@ import { ViewEditorProgressChangeId } from "@dataservices/virtualization/view-ed }) export class ViewEditorHeaderComponent implements OnInit, OnDestroy { - // used by html - public readonly viewDescriptionLabel = ViewEditorI18n.viewDescriptionLabel; - public readonly viewDescriptionPlaceholder = ViewEditorI18n.viewDescriptionPlaceholder; - - public ngxTableConfig: NgxDataTableConfig; - public tableConfig: TableConfig; - public tableColumns: any[] = []; - public tableRows: ViewDefinition[] = []; - private emptyStateConfig: EmptyStateConfig; - - private readonly logger: LoggerService; private readonly editorService: ViewEditorService; - private subscription: Subscription; - private modalService: BsModalService; - private dataserviceService: DataserviceService; - private selectionService: SelectionService; - private viewsLoadingState: LoadingState = LoadingState.LOADING; - private selectedVirtualization: Dataservice; - private viewSavedUponCompletion: ViewDefinition; - constructor( editorService: ViewEditorService, - dataserviceService: DataserviceService, - selectionService: SelectionService, - logger: LoggerService, - modalService: BsModalService) { + constructor( editorService: ViewEditorService) { this.editorService = editorService; - this.dataserviceService = dataserviceService; - this.selectionService = selectionService; - this.logger = logger; - this.modalService = modalService; - } - - /** - * @param {ViewEditorEvent} event the event being processed - */ - public handleEditorEvent( event: ViewEditorEvent ): void { - this.logger.debug( "ViewEditorHeaderComponent received event: " + event.toString() ); - - if ( event.typeIsEditorViewSaveProgressChanged() ) { - if ( event.args.length !== 0 ) { - // Detect changes in view editor save progress - if ( event.args[ 0 ] === ViewEditorProgressChangeId.COMPLETED_SUCCESS || - event.args[ 0 ] === ViewEditorProgressChangeId.COMPLETED_FAILED ) { - if (this.viewSavedUponCompletion && this.viewSavedUponCompletion !== null) { - this.createNewView(this.viewSavedUponCompletion); - } - } - } - } } /** * Cleanup code when destroying the view editor header. */ public ngOnDestroy(): void { - this.subscription.unsubscribe(); + } /** * Initialization code run after construction. */ public ngOnInit(): void { - this.subscription = this.editorService.editorEvent.subscribe( ( event ) => this.handleEditorEvent( event ) ); - - // ---------------------------------- - // View Table configurations - // ---------------------------------- - this.tableColumns = [ - { - draggable: false, - name: "Views", - prop: "viewName", - resizeable: true, - sortable: false, - width: "100" - } - ]; - - this.ngxTableConfig = { - headerHeight: 0, - rowHeight: 20, - reorderable: false, - selectionType: "'single'" - } as NgxDataTableConfig; - - this.emptyStateConfig = { - title: ViewEditorI18n.noViewsDefined - } as EmptyStateConfig; - - this.tableConfig = { - emptyStateConfig: this.emptyStateConfig - } as TableConfig; - - // init the available views - this.initViews(); - } - - /* - * Initialize the views for the current dataservice. Makes a rest call to get the ViewEditorStates for the serviceVdb - */ - private initViews( ): void { - this.viewsLoadingState = LoadingState.LOADING; - this.selectedVirtualization = this.selectionService.getSelectedVirtualization(); - if ( !this.selectedVirtualization || this.selectedVirtualization === null ) { - this.tableRows = []; - } - - const selectedView = this.selectionService.getSelectedViewDefinition(); - - const vdbName = this.selectedVirtualization.getServiceVdbName(); - const editorStatesPattern = vdbName.toLowerCase() + "*"; - - const self = this; - this.dataserviceService - .getViewEditorStates(editorStatesPattern) - .subscribe( - (viewEditorStates) => { - const viewDefns: ViewDefinition[] = []; - for ( const viewState of viewEditorStates ) { - const viewDefn = viewState.getViewDefinition(); - if ( viewDefn ) { - viewDefns.push( viewDefn ); - } - } - self.tableRows = viewDefns.sort( (left, right): number => { - if (left.getName() < right.getName()) return -1; - if (left.getName() > right.getName()) return 1; - return 0; - }); - - let initialView: ViewDefinition = null; - if (!selectedView || selectedView === null) { - initialView = (self.tableRows && self.tableRows.length > 0) ? self.tableRows[0] : null; - } else { - initialView = self.tableRows.find((x) => x.getName() === selectedView.getName()); - } - self.viewsLoadingState = LoadingState.LOADED_VALID; - self.selectView(initialView); - }, - (error) => { - self.logger.error("[VirtualizationComponent] Error updating the views for the virtualization: %o", error); - self.viewsLoadingState = LoadingState.LOADED_INVALID; - self.tableRows = []; - } - ); } /** @@ -198,55 +52,6 @@ export class ViewEditorHeaderComponent implements OnInit, OnDestroy { return !this.editorService.getEditorView() || this.editorService.isReadOnly(); } - /** - * @returns {boolean} `true` if views are being loaded - */ - public get viewsLoading(): boolean { - return this.viewsLoadingState === LoadingState.LOADING; - } - - /** - * @returns {string} the view description - */ - public get viewDescription(): string { - if ( this.editorService.getEditorView() ) { - return this.editorService.getEditorView().getDescription(); - } - - return ""; - } - - /** - * @param {string} newDescription the new description - */ - public set viewDescription( newDescription: string ) { - if ( this.editorService.getEditorView() ) { - if ( newDescription !== this.editorService.getEditorView().getDescription() ) { - const oldDescription = this.editorService.getEditorView().getDescription(); - const temp = CommandFactory.createUpdateViewDescriptionCommand( newDescription, oldDescription ); - - if ( temp instanceof Command ) { - this.editorService.fireViewStateHasChanged( ViewEditorPart.HEADER, temp as Command ); - } else { - const error = temp as Error; - this.logger.error( error.message ); - } - } - } else { - // shouldn't get here as description text input should be disabled if no view being edited - this.logger.error( "Trying to set description but there is no view being edited" ); - } - } - - /** - * Called when text in the view description textarea changes. - * - * @param {string} newDescription the new description of the view - */ - public viewDescriptionChanged( newDescription: string ): void { - this.viewDescription = newDescription; - } - /** * @returns {string} the name of the dataservice of the view being edited */ @@ -260,233 +65,4 @@ export class ViewEditorHeaderComponent implements OnInit, OnDestroy { // should always have a virtualization name so shouldn't get here return "< error >"; } - - /** - * @returns {string} the description of the dataservice of the view being edited - */ - public get virtualizationDescription(): string { - const virtualization = this.editorService.getEditorVirtualization(); - - if ( virtualization ) { - return virtualization.getDescription(); - } - - // should always have a virtualization description so shouldn't get here - return "< error >"; - } - - public get deleteViewButtonEnabled(): boolean { - return ( this.getSelectedView() !== null ); - } - - /** - * Handles view selection from table - * @param $event - */ - public viewSelectionChanged( $event ): void { - const selectedViews: ViewDefinition[] = $event.selected; - // If the current view has pending changes, auto save it - if ( this.editorService.hasChanges() ) { - this.editorService.saveEditorState(); - } - this.selectView(selectedViews[0]); - } - - /** - * Handle creation of a new View. Displays the createView dialog, - * then saves the viewDefinition and adds it to the list - */ - public onCreateView(): void { - // Open New View dialog - const initialState = { - title: ViewEditorI18n.createViewDialogTitle, - cancelButtonText: ViewEditorI18n.cancelButtonText, - okButtonText: ViewEditorI18n.okButtonText - }; - - // Show Dialog, act upon confirmation click - const modalRef = this.modalService.show(CreateViewDialogComponent, {initialState}); - modalRef.content.okAction.take(1).subscribe((viewDefn) => { - // If the current view has pending changes, save them first - if ( this.editorService.hasChanges() ) { - this.viewSavedUponCompletion = viewDefn; - this.editorService.saveEditorState(); - } else { - this.createNewView(viewDefn); - } - // addition of a view undeploys active serviceVdb - this.editorService.undeploySelectedVirtualization(); - }); - } - - private createNewView(viewDefn: ViewDefinition): void { - const selectedDs = this.selectionService.getSelectedVirtualization(); - const editorId = this.getEditorStateId(selectedDs, viewDefn); - - // Create new editor state to save - const editorState = new ViewEditorState(); - editorState.setId(editorId); - editorState.setViewDefinition(viewDefn); - - const editorStates: ViewEditorState[] = []; - editorStates.push(editorState); - - this.viewsLoadingState = LoadingState.LOADING; - - const self = this; - this.dataserviceService - .saveViewEditorStatesRefreshViews(editorStates, selectedDs.getId()) - .subscribe( - (wasSuccess) => { - // Add the new ViewDefinition to the table - self.addViewDefinitionToList(viewDefn); - self.viewSavedUponCompletion = null; - self.viewsLoadingState = LoadingState.LOADED_VALID; - }, - (error) => { - self.logger.error("[VirtualizationComponent] Error saving the editor state: %o", error); - self.viewSavedUponCompletion = null; - self.viewsLoadingState = LoadingState.LOADED_INVALID; - } - ); - } - - /** - * Construct id for the editor state - * @param {Dataservice} dataservice the dataservice - * @param {ViewDefinition} viewDefn the view definition - * @returns {string} the ID used to persist the editor state - */ - private getEditorStateId(dataservice: Dataservice, viewDefn: ViewDefinition): string { - return dataservice.getServiceVdbName().toLowerCase() + "." + viewDefn.getName(); - } - - /** - * Handle Delete of the selected View - * @param {string} viewName - */ - public onDeleteView( ): void { - const viewName = this.getSelectedView().getName(); - - // Dialog Content - const message = "Do you really want to delete View '" + viewName + "'?"; - const initialState = { - title: "Confirm Delete", - bodyContent: message, - cancelButtonText: "Cancel", - confirmButtonText: "Delete" - }; - - // Show Dialog, act upon confirmation click - const modalRef = this.modalService.show(ConfirmDialogComponent, {initialState}); - modalRef.content.confirmAction.take(1).subscribe((value) => { - this.doDeleteView(viewName); - }); - } - - /** - * Deletes the specified ViewEditorState from the userProfile, and removes ViewDefinition from the current list. - * @param {string} viewDefnName the name of the view - */ - private doDeleteView(viewDefnName: string): void { - const selectedViewDefn = this.tableRows.find((x) => x.getName() === viewDefnName); - const selectedDs = this.selectionService.getSelectedVirtualization(); - const vdbName = selectedDs.getServiceVdbName(); - const editorStateId = vdbName.toLowerCase() + "." + viewDefnName; - const dataserviceName = selectedDs.getId(); - - this.viewsLoadingState = LoadingState.LOADING; - // Note: we can only doDelete selected items that we can see in the UI. - this.logger.debug("[VirtualizationComponent] Deleting selected Virtualization View."); - const self = this; - this.dataserviceService - .deleteViewEditorStateRefreshViews(editorStateId, dataserviceName) - .subscribe( - (wasSuccess) => { - self.removeViewDefinitionFromList(selectedViewDefn); - // deletion of a view undeploys active serviceVdb - self.editorService.undeploySelectedVirtualization(); - this.viewsLoadingState = LoadingState.LOADED_VALID; - }, - (error) => { - self.logger.error("[VirtualizationComponent] Error deleting the editor state: %o", error); - this.viewsLoadingState = LoadingState.LOADED_INVALID; - } - ); - } - - /* - * Add the specified ViewDefinition to the view definitions table - * @param {ViewDefinition} viewDefn the view definition to add - */ - private addViewDefinitionToList(viewDefn: ViewDefinition): void { - const newRows: ViewDefinition[] = []; - newRows.push(viewDefn); - for ( const row of this.tableRows ) { - if ( row.getName() !== viewDefn.getName() ) { - newRows.push( row ); - } - } - this.tableRows = newRows.sort( (left, right): number => { - if (left.getName() < right.getName()) return -1; - if (left.getName() > right.getName()) return 1; - return 0; - }); - this.selectView(viewDefn); - } - - /* - * Remove the specified ViewDefinition from the view definitions table - * @param {ViewDefinition} viewDefn the view definition to remove - */ - private removeViewDefinitionFromList(viewDefn: ViewDefinition): void { - const origIndex = this.tableRows.findIndex( ( defn ) => defn.getName() === viewDefn.getName() ); - - const newRows: ViewDefinition[] = []; - for ( const row of this.tableRows ) { - if ( row.getName() !== viewDefn.getName() ) { - newRows.push( row ); - } - } - this.tableRows = newRows; - - // auto select another row - if ( this.tableRows.length > origIndex ) { - this.selectView( this.tableRows[origIndex] ); - } else if ( this.tableRows.length > 0 ) { - this.selectView( this.tableRows[origIndex - 1] ); - } else if ( this.tableRows.length === 0 ) { - this.selectView( null ); - } - } - - private selectView( selView: ViewDefinition ): void { - // Updates table selection display - let viewSelection = null; - if ( selView && selView !== null ) { - for (const view of this.tableRows) { - if (view.getName() === selView.getName()) { - view.setSelected(true); - viewSelection = view; - } else { - view.setSelected(false); - } - } - } - // Update selection service, then fire event - this.selectionService.setSelectedViewDefinition(this.selectedVirtualization, viewSelection); - this.editorService.setEditorView(viewSelection, ViewEditorPart.HEADER); - } - - private getSelectedView( ): ViewDefinition { - let selectedView: ViewDefinition = null; - for (const view of this.tableRows) { - if (view.selected) { - selectedView = view; - break; - } - } - return selectedView; - } - } diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor.component.css b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor.component.css index 8cae6216..9a69e1f9 100644 --- a/ngapp/src/app/dataservices/virtualization/view-editor/view-editor.component.css +++ b/ngapp/src/app/dataservices/virtualization/view-editor/view-editor.component.css @@ -90,6 +90,8 @@ */ #view-editor-toolbar .form-group { margin: 2px; + padding-left: 5px; + padding-right: 5px; } /* @@ -99,7 +101,7 @@ background-color: inherit; border: none; box-shadow: none; - padding: 0; + padding: 0px; } /* diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.css b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.css new file mode 100644 index 00000000..c0ccdd80 --- /dev/null +++ b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.css @@ -0,0 +1,79 @@ +.list-pf-container { + -ms-flex-align: start; + align-items: flex-start; + display: -ms-flexbox; + display: flex; + padding: 0; +} + +.views-list-title { + padding-left: 10px; + text-align: left; +} + +.create-delete-buttons { + horiz-align: left; + padding-top: 5px; + padding-bottom: 5px; + grid-row-gap: 1px; + vertical-align: middle; +} + +.views-list-create-button { + margin-left: 0px; + padding-left: 0; + padding-top: 8px; + padding-bottom: 8px; + background-color: aquamarine; +} + +.views-list-delete-button { + padding-left: 0px; + padding-top: 8px; + padding-bottom: 8px; +} + +.views-list-description-area { + padding-left: 0; + margin-left: 0; +} + +.views-list-div { + padding-left: 1px; + padding-right: 1px; + margin-bottom: 5px; + min-height: 400px; + max-height: 400px; + height: 100%; + width: 100%; + border: 1px inset grey; + overflow-y: auto; +} + +/* + * Style the empty state component so that it is centered and extends the entire width. + */ +#views-list-table .blank-slate-pf { + background-color: inherit; + min-width: 200px; + border: none; + padding: 0; + min-height: 400px; + max-height: 400px; +} + +/* + * Style text in blank slate for table + */ +#views-list-table h1, .h1 { + font-size: 14px; +} + +/* + * The view description text area. + */ +#views-list-description-input { + min-height: 20px; + min-width: 300px; + vertical-align: top; +} diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.html b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.html new file mode 100644 index 00000000..18ccdd3b --- /dev/null +++ b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.html @@ -0,0 +1,37 @@ +
+ +
+
+ Views +
+ + + +
+ +
+
+ +
+
+ + + + + +
+
+ + +
+
+ +
diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.spec.ts b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.spec.ts new file mode 100644 index 00000000..50ea4c2c --- /dev/null +++ b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.spec.ts @@ -0,0 +1,66 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ViewsListComponent } from './views-list.component'; +import {SelectionService} from "@core/selection.service"; +import {Dataservice} from "@dataservices/shared/dataservice.model"; +import {ViewEditorService} from "@dataservices/virtualization/view-editor/view-editor.service"; +import {MockAppSettingsService} from "@core/mock-app-settings.service"; +import {LoggerService} from "@core/logger.service"; +import {AppSettingsService} from "@core/app-settings.service"; +import {DataserviceService} from "@dataservices/shared/dataservice.service"; +import {MockVdbService} from "@dataservices/shared/mock-vdb.service"; +import {BsModalService, ComponentLoaderFactory} from "ngx-bootstrap"; +import {MockDataserviceService} from "@dataservices/shared/mock-dataservice.service"; +import {NotifierService} from "@dataservices/shared/notifier.service"; +import {VdbService} from "@dataservices/shared/vdb.service"; +import {HttpModule} from "@angular/http"; +import {FormsModule} from "@angular/forms"; +import {TableModule} from "patternfly-ng"; +import {RouterTestingModule} from "@angular/router/testing"; + +describe('ViewsListComponent', () => { + let component: ViewsListComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + FormsModule, + HttpModule, + RouterTestingModule, + TableModule + ], + declarations: [ ViewsListComponent ], + providers: [ + BsModalService, + { provide: AppSettingsService, useClass: MockAppSettingsService }, + { provide: DataserviceService, useClass: MockDataserviceService }, + LoggerService, + NotifierService, + SelectionService, + { provide: VdbService, useClass: MockVdbService }, + ComponentLoaderFactory, + ViewEditorService + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ViewsListComponent); + component = fixture.componentInstance; + + const selService = TestBed.get( SelectionService ); + const ds: Dataservice = new Dataservice(); + ds.setId("testDs"); + ds.setServiceVdbName("testDsVdb"); + // noinspection JSUnusedAssignment + selService.setSelectedVirtualization( ds ); + + fixture.detectChanges(); + }); + + it('should be created', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.ts b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.ts new file mode 100644 index 00000000..4ab1a34a --- /dev/null +++ b/ngapp/src/app/dataservices/virtualization/view-editor/views-list/views-list.component.ts @@ -0,0 +1,468 @@ +/** + * @license + * Copyright 2017 JBoss Inc + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {AfterViewInit, Component, OnDestroy, OnInit, ViewEncapsulation} from '@angular/core'; +import {LoadingState} from "@shared/loading-state.enum"; +import {ViewDefinition} from "@dataservices/shared/view-definition.model"; +import {ViewEditorPart} from "@dataservices/virtualization/view-editor/view-editor-part.enum"; +import {EmptyStateConfig, NgxDataTableConfig, TableConfig} from "patternfly-ng"; +import {ViewEditorI18n} from "@dataservices/virtualization/view-editor/view-editor-i18n"; +import {ViewEditorProgressChangeId} from "@dataservices/virtualization/view-editor/event/view-editor-save-progress-change-id.enum"; +import {ViewEditorState} from "@dataservices/shared/view-editor-state.model"; +import {ViewEditorService} from "@dataservices/virtualization/view-editor/view-editor.service"; +import {Dataservice} from "@dataservices/shared/dataservice.model"; +import {LoggerService} from "@core/logger.service"; +import {ViewEditorEvent} from "@dataservices/virtualization/view-editor/event/view-editor-event"; +import {Subscription} from "rxjs/Subscription"; +import {BsModalService} from "ngx-bootstrap"; +import {DataserviceService} from "@dataservices/shared/dataservice.service"; +import {SelectionService} from "@core/selection.service"; +import {ConfirmDialogComponent} from "@shared/confirm-dialog/confirm-dialog.component"; +import {CommandFactory} from "@dataservices/virtualization/view-editor/command/command-factory"; +import {Command} from "@dataservices/virtualization/view-editor/command/command"; +import {CreateViewDialogComponent} from "@dataservices/virtualization/view-editor/create-view-dialog/create-view-dialog.component"; + +@Component({ + encapsulation: ViewEncapsulation.None, + selector: 'app-views-list', + templateUrl: './views-list.component.html', + styleUrls: ['./views-list.component.css'] +}) +export class ViewsListComponent implements OnInit, OnDestroy, AfterViewInit { + + // used by html + public readonly viewDescriptionLabel = ViewEditorI18n.viewDescriptionLabel; + public readonly viewDescriptionPlaceholder = ViewEditorI18n.viewDescriptionPlaceholder; + + public ngxTableConfig: NgxDataTableConfig; + public tableConfig: TableConfig; + public tableColumns: any[] = []; + public tableRows: ViewDefinition[] = []; + private emptyStateConfig: EmptyStateConfig; + + private readonly logger: LoggerService; + private readonly editorService: ViewEditorService; + private subscription: Subscription; + private modalService: BsModalService; + private dataserviceService: DataserviceService; + private selectionService: SelectionService; + private viewsLoadingState: LoadingState = LoadingState.LOADING; + private selectedVirtualization: Dataservice; + private viewSavedUponCompletion: ViewDefinition; + + constructor( editorService: ViewEditorService, + dataserviceService: DataserviceService, + selectionService: SelectionService, + logger: LoggerService, + modalService: BsModalService) { + this.editorService = editorService; + this.dataserviceService = dataserviceService; + this.selectionService = selectionService; + this.logger = logger; + this.modalService = modalService; + } + + /** + * @param {ViewEditorEvent} event the event being processed + */ + public handleEditorEvent( event: ViewEditorEvent ): void { + this.logger.debug( "ViewsListComponent received event: " + event.toString() ); + + if ( event.typeIsEditorViewSaveProgressChanged() ) { + if ( event.args.length !== 0 ) { + // Detect changes in view editor save progress + if ( event.args[ 0 ] === ViewEditorProgressChangeId.COMPLETED_SUCCESS || + event.args[ 0 ] === ViewEditorProgressChangeId.COMPLETED_FAILED ) { + if (this.viewSavedUponCompletion && this.viewSavedUponCompletion !== null) { + this.createNewView(this.viewSavedUponCompletion); + } + } + } + } + } + + /** + * Cleanup code when destroying the view editor header. + */ + public ngOnDestroy(): void { + this.subscription.unsubscribe(); + } + + /** + * Initialization code run after construction. + */ + public ngOnInit(): void { + this.subscription = this.editorService.editorEvent.subscribe( ( event ) => this.handleEditorEvent( event ) ); + + // ---------------------------------- + // View Table configurations + // ---------------------------------- + this.tableColumns = [ + { + draggable: false, + name: "Views", + prop: "viewName", + resizeable: true, + sortable: false, + width: "100" + } + ]; + + this.ngxTableConfig = { + headerHeight: 0, + rowHeight: 20, + reorderable: false, + selectionType: "'single'" + } as NgxDataTableConfig; + + this.emptyStateConfig = { + title: ViewEditorI18n.noViewsDefined + } as EmptyStateConfig; + + this.tableConfig = { + emptyStateConfig: this.emptyStateConfig + } as TableConfig; + + + } + + public ngAfterViewInit(): void { + // init the available views + this.initViews(); + } + + /* + * Initialize the views for the current dataservice. Makes a rest call to get the ViewEditorStates for the serviceVdb + */ + private initViews( ): void { + this.viewsLoadingState = LoadingState.LOADING; + this.selectedVirtualization = this.selectionService.getSelectedVirtualization(); + if ( !this.selectedVirtualization || this.selectedVirtualization === null ) { + this.tableRows = []; + } + + const selectedView = this.selectionService.getSelectedViewDefinition(); + + const vdbName = this.selectedVirtualization.getServiceVdbName(); + const editorStatesPattern = vdbName.toLowerCase() + "*"; + + const self = this; + this.dataserviceService + .getViewEditorStates(editorStatesPattern) + .subscribe( + (viewEditorStates) => { + const viewDefns: ViewDefinition[] = []; + for ( const viewState of viewEditorStates ) { + const viewDefn = viewState.getViewDefinition(); + if ( viewDefn ) { + viewDefns.push( viewDefn ); + } + } + self.tableRows = viewDefns.sort( (left, right): number => { + if (left.getName() < right.getName()) return -1; + if (left.getName() > right.getName()) return 1; + return 0; + }); + + let initialView: ViewDefinition = null; + if (!selectedView || selectedView === null) { + initialView = (self.tableRows && self.tableRows.length > 0) ? self.tableRows[0] : null; + } else { + initialView = self.tableRows.find((x) => x.getName() === selectedView.getName()); + } + self.viewsLoadingState = LoadingState.LOADED_VALID; + if( initialView !== null) { + self.selectView(initialView); + } + }, + (error) => { + self.logger.error("[VirtualizationComponent] Error updating the views for the virtualization: %o", error); + self.viewsLoadingState = LoadingState.LOADED_INVALID; + self.tableRows = []; + } + ); + } + + /** + * @returns {boolean} `true` if view being edited is readonly + */ + public get readOnly(): boolean { + return !this.editorService.getEditorView() || this.editorService.isReadOnly(); + } + + /** + * @returns {boolean} `true` if views are being loaded + */ + public get viewsLoading(): boolean { + return this.viewsLoadingState === LoadingState.LOADING; + } + + /** + * Handles view selection from table + * @param $event + */ + public viewSelectionChanged( $event ): void { + const selectedViews: ViewDefinition[] = $event.selected; + // If the current view has pending changes, auto save it + if ( this.editorService.hasChanges() ) { + this.editorService.saveEditorState(); + } + this.selectView(selectedViews[0]); + } + + public get deleteViewButtonEnabled(): boolean { + return ( this.getSelectedView() !== null ); + } + + private createNewView(viewDefn: ViewDefinition): void { + const selectedDs = this.selectionService.getSelectedVirtualization(); + const editorId = this.getEditorStateId(selectedDs, viewDefn); + + // Create new editor state to save + const editorState = new ViewEditorState(); + editorState.setId(editorId); + editorState.setViewDefinition(viewDefn); + + const editorStates: ViewEditorState[] = []; + editorStates.push(editorState); + + this.viewsLoadingState = LoadingState.LOADING; + + const self = this; + this.dataserviceService + .saveViewEditorStatesRefreshViews(editorStates, selectedDs.getId()) + .subscribe( + (wasSuccess) => { + // Add the new ViewDefinition to the table + self.addViewDefinitionToList(viewDefn); + self.viewSavedUponCompletion = null; + self.viewsLoadingState = LoadingState.LOADED_VALID; + }, + (error) => { + self.logger.error("[VirtualizationComponent] Error saving the editor state: %o", error); + self.viewSavedUponCompletion = null; + self.viewsLoadingState = LoadingState.LOADED_INVALID; + } + ); + } + + /** + * Handle creation of a new View. Displays the createView dialog, + * then saves the viewDefinition and adds it to the list + */ + public onCreateView(): void { + // Open New View dialog + const initialState = { + title: ViewEditorI18n.createViewDialogTitle, + cancelButtonText: ViewEditorI18n.cancelButtonText, + okButtonText: ViewEditorI18n.okButtonText + }; + + // Show Dialog, act upon confirmation click + const modalRef = this.modalService.show(CreateViewDialogComponent, {initialState}); + modalRef.content.okAction.take(1).subscribe((viewDefn) => { + // If the current view has pending changes, save them first + if ( this.editorService.hasChanges() ) { + this.viewSavedUponCompletion = viewDefn; + this.editorService.saveEditorState(); + } else { + this.createNewView(viewDefn); + } + // addition of a view undeploys active serviceVdb + this.editorService.undeploySelectedVirtualization(); + }); + } + + /** + * Construct id for the editor state + * @param {Dataservice} dataservice the dataservice + * @param {ViewDefinition} viewDefn the view definition + * @returns {string} the ID used to persist the editor state + */ + private getEditorStateId(dataservice: Dataservice, viewDefn: ViewDefinition): string { + return dataservice.getServiceVdbName().toLowerCase() + "." + viewDefn.getName(); + } + + /** + * Handle Delete of the selected View + * @param {string} viewName + */ + public onDeleteView( ): void { + const viewName = this.getSelectedView().getName(); + + // Dialog Content + const message = "Do you really want to delete View '" + viewName + "'?"; + const initialState = { + title: "Confirm Delete", + bodyContent: message, + cancelButtonText: "Cancel", + confirmButtonText: "Delete" + }; + + // Show Dialog, act upon confirmation click + const modalRef = this.modalService.show(ConfirmDialogComponent, {initialState}); + modalRef.content.confirmAction.take(1).subscribe((value) => { + this.doDeleteView(viewName); + }); + } + + /** + * Deletes the specified ViewEditorState from the userProfile, and removes ViewDefinition from the current list. + * @param {string} viewDefnName the name of the view + */ + private doDeleteView(viewDefnName: string): void { + const selectedViewDefn = this.tableRows.find((x) => x.getName() === viewDefnName); + const selectedDs = this.selectionService.getSelectedVirtualization(); + const vdbName = selectedDs.getServiceVdbName(); + const editorStateId = vdbName.toLowerCase() + "." + viewDefnName; + const dataserviceName = selectedDs.getId(); + + this.viewsLoadingState = LoadingState.LOADING; + // Note: we can only doDelete selected items that we can see in the UI. + this.logger.debug("[VirtualizationComponent] Deleting selected Virtualization View."); + const self = this; + this.dataserviceService + .deleteViewEditorStateRefreshViews(editorStateId, dataserviceName) + .subscribe( + (wasSuccess) => { + self.removeViewDefinitionFromList(selectedViewDefn); + // deletion of a view undeploys active serviceVdb + self.editorService.undeploySelectedVirtualization(); + this.viewsLoadingState = LoadingState.LOADED_VALID; + }, + (error) => { + self.logger.error("[VirtualizationComponent] Error deleting the editor state: %o", error); + this.viewsLoadingState = LoadingState.LOADED_INVALID; + } + ); + } + + /* + * Add the specified ViewDefinition to the view definitions table + * @param {ViewDefinition} viewDefn the view definition to add + */ + private addViewDefinitionToList(viewDefn: ViewDefinition): void { + const newRows: ViewDefinition[] = []; + newRows.push(viewDefn); + for ( const row of this.tableRows ) { + if ( row.getName() !== viewDefn.getName() ) { + newRows.push( row ); + } + } + this.tableRows = newRows.sort( (left, right): number => { + if (left.getName() < right.getName()) return -1; + if (left.getName() > right.getName()) return 1; + return 0; + }); + this.selectView(viewDefn); + } + + /* + * Remove the specified ViewDefinition from the view definitions table + * @param {ViewDefinition} viewDefn the view definition to remove + */ + private removeViewDefinitionFromList(viewDefn: ViewDefinition): void { + const origIndex = this.tableRows.findIndex( ( defn ) => defn.getName() === viewDefn.getName() ); + + const newRows: ViewDefinition[] = []; + for ( const row of this.tableRows ) { + if ( row.getName() !== viewDefn.getName() ) { + newRows.push( row ); + } + } + this.tableRows = newRows; + + // auto select another row + if ( this.tableRows.length > origIndex ) { + this.selectView( this.tableRows[origIndex] ); + } else if ( this.tableRows.length > 0 ) { + this.selectView( this.tableRows[origIndex - 1] ); + } else if ( this.tableRows.length === 0 ) { + this.selectView( null ); + } + } + + private selectView( selView: ViewDefinition ): void { + // Updates table selection display + let viewSelection = null; + if ( selView && selView !== null ) { + for (const view of this.tableRows) { + if (view.getName() === selView.getName()) { + view.setSelected(true); + viewSelection = view; + } else { + view.setSelected(false); + } + } + } + // Update selection service, then fire event + this.selectionService.setSelectedViewDefinition(this.selectedVirtualization, viewSelection); + this.editorService.setEditorView(viewSelection, ViewEditorPart.HEADER); + } + + private getSelectedView( ): ViewDefinition { + let selectedView: ViewDefinition = null; + for (const view of this.tableRows) { + if (view.selected) { + selectedView = view; + break; + } + } + return selectedView; + } + + /** + * @returns {string} the view description + */ + public get viewDescription(): string { + if ( this.editorService.getEditorView() ) { + return this.editorService.getEditorView().getDescription(); + } + + return ""; + } + + /** + * @param {string} newDescription the new description + */ + public set viewDescription( newDescription: string ) { + if ( this.editorService.getEditorView() ) { + if ( newDescription !== this.editorService.getEditorView().getDescription() ) { + const oldDescription = this.editorService.getEditorView().getDescription(); + const temp = CommandFactory.createUpdateViewDescriptionCommand( newDescription, oldDescription ); + + if ( temp instanceof Command ) { + this.editorService.fireViewStateHasChanged( ViewEditorPart.HEADER, temp as Command ); + } else { + const error = temp as Error; + this.logger.error( error.message ); + } + } + } else { + // shouldn't get here as description text input should be disabled if no view being edited + this.logger.error( "Trying to set description but there is no view being edited" ); + } + } + + /** + * Called when text in the view description textarea changes. + * + * @param {string} newDescription the new description of the view + */ + public viewDescriptionChanged( newDescription: string ): void { + this.viewDescription = newDescription; + } +} diff --git a/ngapp/src/assets/table.png b/ngapp/src/assets/table.png new file mode 100644 index 00000000..99a133d4 Binary files /dev/null and b/ngapp/src/assets/table.png differ