This is an automated email from the ASF dual-hosted git repository. dgnatyshyn pushed a commit to branch bucket-browser-gcp in repository https://gitbox.apache.org/repos/asf/incubator-dlab.git
The following commit(s) were added to refs/heads/bucket-browser-gcp by this push: new 3b3a0fe [Dlab 1551]: Fixed bug file download (#708) 3b3a0fe is described below commit 3b3a0fea00f6f0e4d4f92cdd63913d1e02afbf1f Author: Dmytro Gnatyshyn <42860905+dg1...@users.noreply.github.com> AuthorDate: Mon Apr 27 17:10:24 2020 +0300 [Dlab 1551]: Fixed bug file download (#708) [DLAB-1551]: Bucket browser on UI --- .../administration/project/project.component.ts | 1 - .../core/interceptors/http.token.interceptor.ts | 10 +- .../src/app/core/services/appRouting.service.ts | 1 + .../services/applicationServiceFacade.service.ts | 21 ++-- .../app/core/services/bucket-browser.service.ts | 95 +---------------- .../webapp/src/app/core/util/fileUtils.ts | 10 ++ .../bucket-browser/bucket-browser.component.html | 29 +++--- .../bucket-browser/bucket-browser.component.scss | 16 +++ .../bucket-browser/bucket-browser.component.ts | 107 +++++++------------ .../bucket-browser/bucket-data.service.ts | 108 ++++++++++++++++++++ .../folder-tree/folder-tree.component.ts | 113 ++++++++++++--------- .../detail-dialog/detail-dialog.component.html | 4 +- .../detail-dialog/detail-dialog.component.ts | 4 +- .../webapp/src/app/resources/resources.module.ts | 3 + 14 files changed, 292 insertions(+), 230 deletions(-) diff --git a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts index 9833a40..ef71ca7 100644 --- a/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/administration/project/project.component.ts @@ -96,7 +96,6 @@ export class ProjectComponent implements OnInit, OnDestroy { if (this.projectList.length) this.dialog.open(EditProjectComponent, { data: { action: 'create', item: null }, panelClass: 'modal-xl-s' }) .afterClosed().subscribe(() => { - console.log('Create project'); this.getEnvironmentHealthStatus(); }); } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts index 29aa010..9d72d0b 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/interceptors/http.token.interceptor.ts @@ -33,7 +33,15 @@ import { Observable } from 'rxjs'; if (token) headersConfig['Authorization'] = `Bearer ${token}`; - if (!request.headers.has('Content-Type') && !request.headers.has('Upload') && request.url.indexOf('upload') === -1) + // if (request.url.indexOf('api/bucket') !== -1) { + // headersConfig['Authorization'] = `Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJfSEVvZmliX2djZXJpcllidE51dVBoSk81OEJNOFc5M1dHZW9VR3hTR2l3In0.eyJqdGkiOiIxY2E4OTQ1OS02MDU5LTQzOTctYTZhMy1kMzY5YTY0OTkyNzIiLCJleHAiOjE1ODc5OTUyNjgsIm5iZiI6MCwiaWF0IjoxNTg3OTk0OTY4LCJpc3MiOiJodHRwczovL2lkcC5kZW1vLmRsYWJhbmFseXRpY3MuY29tL2F1dGgvcmVhbG1zL2RsYWIiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNWIwYWEwMmYtYTU3ZS00MGM0LTk4ODQtNDlmYmU5OGViMzU4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoib2Z1a3MtMTMwNC11aSIsIm [...] + // } + + if (!request.headers.has('Content-Type') + && !request.headers.has('Upload') + && request.url.indexOf('upload') === -1 + && request.url.indexOf('download') === -1) + headersConfig['Content-Type'] = 'application/json; charset=UTF-8'; const header = request.clone({ setHeaders: headersConfig }); diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts index 1a80759..0f05bcb 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/appRouting.service.ts @@ -34,6 +34,7 @@ export class AppRoutingService { } redirectToHomePage(): void { + console.log('redirect'); this.router.navigate(['/resources_list']); } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts index ebaec6d..792b748 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/applicationServiceFacade.service.ts @@ -22,7 +22,6 @@ import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { Dictionary } from '../collections'; - import { environment } from '../../../environments/environment'; import { HTTPMethod } from '../util'; @@ -254,10 +253,11 @@ export class ApplicationServiceFacade { null); } - public buildGetBucketData(): Observable<any> { + + public buildGetBucketData(data): Observable<any> { return this.buildRequest(HTTPMethod.GET, - this.requestRegistry.Item(ApplicationServiceFacade.BUCKET) + '/ofuks-1304-prj1-local-bucket/endpoint/local', - null); + this.requestRegistry.Item(ApplicationServiceFacade.BUCKET), + data); } public buildUploadFileToBucket(data): Observable<any> { @@ -266,10 +266,12 @@ export class ApplicationServiceFacade { data); } - public buildDownloadFileFromBucket(data): Observable<any> { + public buildDownloadFileFromBucket(data) { return this.buildRequest(HTTPMethod.GET, this.requestRegistry.Item(ApplicationServiceFacade.BUCKET), - data, { observe: 'response', responseType: 'text' } ); + data, { dataType : 'binary', + processData : false, + responseType : 'arraybuffer' } ); } public buildDeleteFileFromBucket(data): Observable<any> { @@ -722,6 +724,9 @@ export class ApplicationServiceFacade { private buildRequest(method: HTTPMethod, url_path: string, body: any, opt?) { // added to simplify development process const url = environment.production ? url_path : API_URL + url_path; + // if (url_path.indexOf('/api/bucket') !== -1) { + // url = 'https://35.233.183.55' + url_path; + // } if (method === HTTPMethod.POST) { return this.http.post(url, body, opt); @@ -729,6 +734,8 @@ export class ApplicationServiceFacade { return this.http.delete(body ? url + JSON.parse(body) : url, opt); } else if (method === HTTPMethod.PUT) { return this.http.put(url, body, opt); - } else return this.http.get(body ? (url + body) : url, opt); + } else { + return this.http.get(body ? (url + body) : url, opt); + } } } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/bucket-browser.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/bucket-browser.service.ts index ff27ef5..0cb8557 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/bucket-browser.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/bucket-browser.service.ts @@ -1,8 +1,11 @@ import { Injectable } from '@angular/core'; -import {BehaviorSubject, Observable} from 'rxjs'; + +import {Observable} from 'rxjs'; import {catchError, map} from 'rxjs/operators'; import {ErrorUtils} from '../util'; import {ApplicationServiceFacade} from './applicationServiceFacade.service'; +import {insideWorkspace} from '@angular/cli/utilities/project'; + export class TodoItemNode { children: TodoItemNode[]; @@ -11,46 +14,12 @@ export class TodoItemNode { size: number; } -/** Flat to-do item node with expandable and level information */ export class TodoItemFlatNode { item: string; level: number; expandable: boolean; } -/** - * The Json object for to-do list data. - */ - - -const array = [{'bucket': 'ofuks-1304-prj1-local-bucket', 'object': '4.txt', 'size': '18 bytes', 'creationDate': '21-4-2020 11:36:36'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': '5.txt', 'size': '18 bytes', 'creationDate': '21-4-2020 11:56:46'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': 'Untitled', 'size': '5 bytes', 'creationDate': '13-4-2020 03:39:11'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': 'adasdas', 'size': '1 KB', 'creationDate': '15-4-2020 02:17 [...] - -const processFiles = (files, target) => { - let pointer = target; - files.forEach((file, index) => { - if (!pointer[file]) { - pointer[file] = {}; - } - pointer = pointer[file]; - }); - -}; - -const processFolderArray = (acc, curr) => { - const files = curr.object.split('/').filter(x => x.length > 0); - processFiles(files, acc); - return acc; -}; - -const convertToFolderTree = (data) => data - .reduce( - processFolderArray, - {} - ); - -const TREE_DATA = convertToFolderTree(array); - - @Injectable({ providedIn: 'root' }) @@ -71,58 +40,6 @@ export class BucketBrowserService { catchError(ErrorUtils.handleServiceError)); } - public initialize() { - let backetData = []; - this.getBacketData().subscribe(v => { - this.serverData = v; - backetData = convertToFolderTree(v); - const data = this.buildFileTree({'ofuks-1304-prj1-local-bucket': backetData}, 0); - this.dataChange.next(data); - }); - // this.serverData = array; - // backetData = convertToFolderTree(this.serverData); - // const data = this.buildFileTree({'ofuks-1304-prj1-local-bucket': backetData}, 0); - // this.dataChange.next(data); - } - - /** - * Build the file structure tree. The `value` is the Json object, or a sub-tree of a Json object. - * The return value is the list of `TodoItemNode`. - */ - public buildFileTree(obj: {[key: string]: any}, level: number): TodoItemNode[] { - return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => { - const value = obj[key]; - const node = new TodoItemNode(); - node.item = key; - if (Object.keys(value).length) { - if (typeof value === 'object') { - node.children = this.buildFileTree(value, level + 1); - } else { - node.item = value; - } - } else { - node.size = this.serverData.filter(v => v.object.indexOf(node.item) !== -1)[0]; - } - return accumulator.concat(node); - }, []); - } - - public insertItem(parent: TodoItemNode, name, isFile) { - if (parent.children) { - if (isFile) { - parent.children.push(name as TodoItemNode); - } else { - parent.children.unshift({item: name, children: []} as TodoItemNode); - this.dataChange.next(this.data); - } - } - } - - public updateItem(node: TodoItemNode, file) { - node.item = file; - this.dataChange.next(this.data); - } - public downloadFile(data) { return this.applicationServiceFacade .buildDownloadFileFromBucket(data) @@ -147,7 +64,5 @@ export class BucketBrowserService { map(response => response), catchError(ErrorUtils.handleServiceError)); } - // initBucket(bucketType) { - // bucketType !== 'project' ? TREE_DATA = local : TREE_DATA = projecta; - // } + } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/util/fileUtils.ts b/services/self-service/src/main/resources/webapp/src/app/core/util/fileUtils.ts index 98ec0f5..d1c2628 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/util/fileUtils.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/util/fileUtils.ts @@ -37,6 +37,16 @@ export class FileUtils { window.URL.revokeObjectURL(url); } + public static downloadBigFiles(data, fileName) { + const a = document.createElement('a'); + document.body.appendChild(a); + const blob = new Blob([data], { type: 'octet/stream' }), + url = window.URL.createObjectURL(blob); + a.href = url; + a.download = fileName; + a.click(); + window.URL.revokeObjectURL(url); + } public static copyToClipboard(val: string) { const selBox = document.createElement('textarea'); diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html index 8671562..3feda30 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.html @@ -22,7 +22,8 @@ <h4 class="modal-title">Bucket browser</h4> <button type="button" class="close" (click)="dialogRef.close()">×</button> </header> - <div class="dialog-content tabs"> + + <div class="dialog-content tabs" [hidden]="!path" > <div class="submit m-bott-10 m-top-10"> <button mat-raised-button type="button" class="butt action"><input type="file" (change)="handleFileInput($event)">Add file</button> <button mat-raised-button type="button" class="butt action" (click)="folderTreeComponent.addNewItem(selectedFolder, '', false)">Create folder</button> @@ -52,10 +53,10 @@ <span class="item-name">{{file.item}}</span> </div> <div class="size">{{file.size.size}}</div> - <div class="size">{{file.size.creationDate }}</div> -<!-- <div class="progress-wrapper">--> -<!--<!– <div class="progres" *ngIf="file.isSelected"><div class="bar"></div></div>–>--> -<!-- </div>--> + <div class="size" *ngIf="!file.isDownloading">{{file.size.lastModifiedDate }}</div> + <div class="progress-wrapper" *ngIf="file.isDownloading"> + <mat-progress-bar mode="indeterminate"></mat-progress-bar> + </div> </div> </li> @@ -64,15 +65,9 @@ <li *ngFor="let file of addedFiles" class="folder-item"> <div class="folder-item-wrapper"> <div class="name">{{file.item}}</div> - <div class="size">{{file.size}} MB</div> + <div class="size">{{file.size}}MB</div> <div class="progress-wrapper"> -<!-- <div class="progres">--> -<!-- <div class="bar" [ngClass]="{'full': isUploading}">--> -<!-- --> -<!-- </div>--> - -<!-- </div>--> - <mat-progress-bar mode="indeterminate" *ngIf="isUploading"></mat-progress-bar> + <mat-progress-bar mode="indeterminate" *ngIf="isUploading"></mat-progress-bar> </div> <div (click)="deleteAddedFile(file)"><i class="material-icons close">close</i></div> </div> @@ -87,4 +82,12 @@ <button *ngIf="this.addedFiles.length !== 0" type="button" class="butt butt-success" mat-raised-button (click)="uploadNewFile()">Upload</button> </div> </div> + + <div class="loading-block" *ngIf="!path"> + <div class="uploading"> + <p>Please wait until DLab loads bucket: <span class="strong">{{data.bucket}}</span>...</p> + <mat-progress-bar mode="indeterminate"></mat-progress-bar> + </div> + </div> + </div> diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss index b9033da..bb16184 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss +++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.scss @@ -18,6 +18,22 @@ */ .bucket-browser { + .loading-block{ + width: 80%; + margin: 20% auto 0 auto; + display: flex; + align-content: center; + justify-content: center; + height: 100%; + .uploading{ + width: 100%; + text-align: center; + p{ + margin-bottom: 20px; + } + } + } + .path{ margin: 0 4px 10px 4px; padding: 4px 4px 4px 20px; diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts index fef7d0d..04b2d3f 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-browser.component.ts @@ -26,6 +26,12 @@ import { ManageUngitService } from '../../core/services'; import {FolderTreeComponent} from './folder-tree/folder-tree.component'; import {BucketBrowserService, TodoItemNode} from '../../core/services/bucket-browser.service'; import {FileUtils} from '../../core/util'; +import {BucketDataService} from './bucket-data.service'; +import FileSaver from 'file-saver'; +import {HttpErrorResponse, HttpEventType} from '@angular/common/http'; +import {catchError, map} from 'rxjs/operators'; +import {of} from 'rxjs'; + @Component({ selector: 'dlab-bucket-browser', @@ -33,7 +39,6 @@ import {FileUtils} from '../../core/util'; styleUrls: ['./bucket-browser.component.scss'] }) export class BucketBrowserComponent implements OnInit { - public filenames: Array<any> = []; public addedFiles = []; public folderItems = []; public path = ''; @@ -55,13 +60,14 @@ export class BucketBrowserComponent implements OnInit { private manageUngitService: ManageUngitService, private _fb: FormBuilder, private bucketBrowserService: BucketBrowserService, + private bucketDataService: BucketDataService, private formBuilder: FormBuilder ) { } ngOnInit() { - // this.bucketBrowserService.getBacketData(); + this.bucketDataService.refreshBucketdata(this.data.bucket, this.data.endpoint); this.endpoint = this.data.endpoint; this.uploadForm = this.formBuilder.group({ file: [''] @@ -74,54 +80,46 @@ export class BucketBrowserComponent implements OnInit { } public handleFileInput(event) { - // for (let i = 0; i < files.length; i++) { - // const file = files[i]; - // const path = file.webkitRelativePath.split('/'); - // } - // } if (event.target.files.length > 0) { const file = event.target.files[0]; this.uploadForm.get('file').setValue(file); - this.filenames = Object['values'](event.target.files).map(v => ({item: v.name, 'size': (v.size / 1048576).toFixed(2)} as unknown as TodoItemNode)); - this.addedFiles = [...this.addedFiles, ...this.filenames]; + const newAddedFiles = Object['values'](event.target.files).map(v => ( + {item: v['name'], 'size': (v['size'] / 1048576).toFixed(2)} as unknown as TodoItemNode )); + this.addedFiles = [...newAddedFiles]; } - } + public toggleSelectedFile(file) { + // remove if when will be possible download several files + if (!file.isSelected) { + this.folderItems.forEach(item => item.isSelected = false); + } + file.isSelected = !file.isSelected; this.selected = this.folderItems.filter(item => item.isSelected); } filesPicked(files) { - // console.log(files); Array.prototype.forEach.call(files, file => { this.addedFiles.push(file.webkitRelativePath); }); - // console.log(this.addedFiles); } public onFolderClick(event) { this.selectedFolder = event.flatNode; this.folderItems = event.element ? event.element.children : event.children; - // this.folderItems = this.folderItems.sort((a, b) => (a.children > b.children) ? 1 : -1) - // console.log(this.folderItems); - this.path = event.path; - this.pathInsideBucket = this.path.indexOf('/') !== -1 ? this.path.slice(this.path.indexOf('/') + 1) + '/' : ''; - this.bucketName = this.path.substring(0, this.path.indexOf('/')) || this.path; - this.folderItems.forEach(item => item.isSelected = false); - } + if (this.folderItems) { + const folders = this.folderItems.filter(v => v.children).sort((a, b) => a.item > b.item ? 1 : -1); + const files = this.folderItems.filter(v => !v.children).sort((a, b) => a.item > b.item ? 1 : -1); + this.folderItems = [...folders, ...files]; + this.path = event.path; + this.pathInsideBucket = this.path.indexOf('/') !== -1 ? this.path.slice(this.path.indexOf('/') + 1) + '/' : ''; + this.bucketName = this.path.substring(0, this.path.indexOf('/')) || this.path; + this.folderItems.forEach(item => item.isSelected = false); + } - private upload(tree, path) { - tree.files.forEach(file => { - this.addedFiles.push(path + file.name); - }); - tree.directories.forEach(directory => { - const newPath = path + directory.name + '/'; - this.addedFiles.push(newPath); - this.upload(directory, newPath); - }); } public deleteAddedFile(file) { @@ -134,31 +132,12 @@ export class BucketBrowserComponent implements OnInit { const formData = new FormData(); formData.append('file', this.uploadForm.get('file').value); formData.append('object', path); - formData.append('bucket', 'ofuks-1304-prj1-local-bucket'); - formData.append('endpoint', this.endpoint); - // file.inProgress = true; + formData.append('bucket', this.bucketName); + formData.append('endpoint', this.endpoint); this.isUploading = true; this.bucketBrowserService.uploadFile(formData) - // .pipe( - // map(event => { - // switch (event.type) { - // case HttpEventType.UploadProgress: - // file.progress = Math.round(event.loaded * 100 / event.total); - // break; - // case HttpEventType.Response: - // return event; - // } - // }), - // catchError((error: HttpErrorResponse) => { - // file.inProgress = false; - // return of(`${file.name} upload failed.`); - // })) .subscribe((event: any) => { - // if (typeof (event) === 'object') { - // console.log(event.body); - // } - // this.isUploading = false; - this.bucketBrowserService.initialize(); + this.bucketDataService.refreshBucketdata(this.data.bucket, this.data.endpoint); this.addedFiles = []; this.isUploading = false; this.toastr.success('File successfully uploaded!', 'Success!'); @@ -168,37 +147,29 @@ export class BucketBrowserComponent implements OnInit { public fileAction(action) { this.selected = this.folderItems.filter(item => item.isSelected); + const selected = this.folderItems.filter(item => item.isSelected)[0]; const path = encodeURIComponent(`${this.pathInsideBucket}${this.selected[0].item}`); if (action === 'download') { - this.bucketBrowserService.downloadFile(`/${this.bucketName}/object/${path}/endpoint/${this.endpoint}/download` - ).subscribe(data => { - FileUtils.downloadFile(data); - // this.downLoadFile(response, 'aplication/octet-stream'); - this.toastr.success('File downloading started!', 'Success!'); + selected['isDownloading'] = true; + this.bucketBrowserService.downloadFile(`/${this.bucketName}/object/${path}/endpoint/${this.endpoint}/download`) + .subscribe(data => { + FileUtils.downloadBigFiles(data, selected.item); + selected['isDownloading'] = false; + this.folderItems.forEach(item => item.isSelected = false); }, error => this.toastr.error(error.message || 'File downloading error!', 'Oops!') ); } if (action === 'delete') { this.bucketBrowserService.deleteFile(`/${this.bucketName}/object/${path}/endpoint/${this.endpoint}`).subscribe(() => { - this.bucketBrowserService.initialize(); + + this.bucketDataService.refreshBucketdata(this.data.bucket, this.data.endpoint); this.toastr.success('File successfully deleted!', 'Success!'); + this.folderItems.forEach(item => item.isSelected = false); }, error => this.toastr.error(error.message || 'File deleting error!', 'Oops!') ); } - - this.folderItems.forEach(item => item.isSelected = false); - this.selected = this.folderItems.filter(item => item.isSelected); } - - // downLoadFile(data: any, type: string) { - // const blob = new Blob([data], { type: type}); - // const url = window.URL.createObjectURL(blob); - // const pwa = window.open(url); - // if (!pwa || pwa.closed || typeof pwa.closed === 'undefined') { - // alert( 'Please disable your Pop-up blocker and try again.'); - // } - // } } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-data.service.ts b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-data.service.ts new file mode 100644 index 0000000..7a169e5 --- /dev/null +++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/bucket-data.service.ts @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 { Injectable } from '@angular/core'; +import { BehaviorSubject, of } from 'rxjs'; +import {BucketBrowserService, TodoItemNode} from '../../core/services/bucket-browser.service'; + +const array = [{'bucket': 'ofuks-1304-prj1-local-bucket', 'object': '4.txt', 'size': '18 bytes', 'creationDate': '21-4-2020 11:36:36'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': '5.txt', 'size': '18 bytes', 'creationDate': '21-4-2020 11:56:46'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': 'Untitled', 'size': '5 bytes', 'creationDate': '13-4-2020 03:39:11'}, {'bucket': 'ofuks-1304-prj1-local-bucket', 'object': 'adasdas', 'size': '1 KB', 'creationDate': '15-4-2020 02:17 [...] + + +@Injectable() +export class BucketDataService { + public _bucketData = new BehaviorSubject<any>(null); + private serverData: any = []; + get data(): TodoItemNode[] { return this._bucketData.value; } + constructor( + private bucketBrowserService: BucketBrowserService, + ) { + } + + public refreshBucketdata(bucket, endpoint) { + let backetData = []; + this.bucketBrowserService.getBacketData(bucket, endpoint).subscribe(v => { + this.serverData = v; + backetData = this.convertToFolderTree(v); + const data = this.buildFileTree({[bucket]: backetData}, 0); + this._bucketData.next(data); + }); + + // this.serverData = array; + // backetData = this.convertToFolderTree(array); + // const data = this.buildFileTree({[bucket]: backetData}, 0); + // this._bucketData.next(data); + // } + } + public buildFileTree(obj: {[key: string]: any}, level: number): TodoItemNode[] { + return Object.keys(obj).reduce<TodoItemNode[]>((accumulator, key) => { + const value = obj[key]; + const node = new TodoItemNode(); + node.item = key; + if (Object.keys(value).length) { + if (typeof value === 'object') { + node.children = this.buildFileTree(value, level + 1); + } else { + node.item = value; + } + } else { + node.size = this.serverData.filter(v => v.object.indexOf(node.item) !== -1)[0]; + } + return accumulator.concat(node); + }, []); + } + + public insertItem(parent: TodoItemNode, name, isFile) { + if (parent.children) { + if (isFile) { + parent.children.push(name as TodoItemNode); + } else { + parent.children.unshift({item: name, children: []} as TodoItemNode); + this._bucketData.next(this.data); + } + } + } + + public updateItem(node: TodoItemNode, file) { + node.item = file; + this._bucketData.next(this.data); + } + + public processFiles = (files, target) => { + let pointer = target; + files.forEach((file, index) => { + if (!pointer[file]) { + pointer[file] = {}; + } + pointer = pointer[file]; + }); + } + + public processFolderArray = (acc, curr) => { + const files = curr.object.split('/').filter(x => x.length > 0); + this.processFiles(files, acc); + return acc; + } + + public convertToFolderTree = (data) => data + .reduce( + this.processFolderArray, + {} + ) + +} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts index 0b367c5..2e93cc1 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/bucket-browser/folder-tree/folder-tree.component.ts @@ -1,8 +1,11 @@ -import {Component, OnInit, AfterViewInit, Output, EventEmitter} from '@angular/core'; +import {Component, OnInit, AfterViewInit, Output, EventEmitter, OnDestroy} from '@angular/core'; import {SelectionModel} from '@angular/cdk/collections'; import {FlatTreeControl} from '@angular/cdk/tree'; import {MatTreeFlatDataSource, MatTreeFlattener} from '@angular/material/tree'; import {BucketBrowserService, TodoItemFlatNode, TodoItemNode} from '../../../core/services/bucket-browser.service'; +import {BucketDataService} from '../bucket-data.service'; +import {Subscription} from 'rxjs'; + @Component({ @@ -10,39 +13,45 @@ import {BucketBrowserService, TodoItemFlatNode, TodoItemNode} from '../../../cor templateUrl: './folder-tree.component.html', styleUrls: ['./folder-tree.component.scss'] }) -export class FolderTreeComponent implements OnInit { - - @Output() showFolderContent: EventEmitter<any> = new EventEmitter(); - path = []; - selectedFolder: TodoItemFlatNode; - flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>(); - nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>(); - selectedParent: TodoItemFlatNode | null = null; - newItemName = ''; - treeControl: FlatTreeControl<TodoItemFlatNode>; - treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>; - dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>; - - checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */); - - constructor(private bucketBrowserService: BucketBrowserService) { - this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, - this.isExpandable, this.getChildren); +export class FolderTreeComponent implements OnInit, OnDestroy { + @Output() showFolderContent: EventEmitter<any> = new EventEmitter(); + private folderTreeSubs; + private path = []; + private selectedFolder: TodoItemFlatNode; + private flatNodeMap = new Map<TodoItemFlatNode, TodoItemNode>(); + private nestedNodeMap = new Map<TodoItemNode, TodoItemFlatNode>(); + private selectedParent: TodoItemFlatNode | null = null; + private newItemName = ''; + private subscriptions: Subscription = new Subscription(); + public treeControl: FlatTreeControl<TodoItemFlatNode>; + private treeFlattener: MatTreeFlattener<TodoItemNode, TodoItemFlatNode>; + public dataSource: MatTreeFlatDataSource<TodoItemNode, TodoItemFlatNode>; + + private checklistSelection = new SelectionModel<TodoItemFlatNode>(true /* multiple */); + + constructor( + private bucketBrowserService: BucketBrowserService, + private bucketDataService: BucketDataService, + ) { + this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren); this.treeControl = new FlatTreeControl<TodoItemFlatNode>(this.getLevel, this.isExpandable); this.dataSource = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener); - bucketBrowserService.dataChange.subscribe(data => { - this.dataSource.data = data; - if (!this.selectedFolder) { + this.subscriptions.add(bucketDataService._bucketData.subscribe(data => { + if (data) { + this.dataSource.data = data; const subject = this.dataSource._flattenedData; - subject.subscribe((subjectData) => { - this.treeControl.expand(subjectData[0]); - this.showItem(subjectData[0]); + this.folderTreeSubs = subject.subscribe((subjectData) => { + if (this.selectedFolder) { + this.selectedFolder = subjectData.filter(v => v.item === this.selectedFolder.item && v.level === this.selectedFolder.level)[0]; + } + this.expandAllParents(this.selectedFolder || subjectData[0]); + this.showItem(this.selectedFolder || subjectData[0]); }); } - }); + })); } getLevel = (node: TodoItemFlatNode) => node.level; @@ -70,14 +79,14 @@ export class FolderTreeComponent implements OnInit { ngOnInit() { - // const subject = this.dataSource._flattenedData; - // subject.subscribe((data) => { - // this.treeControl.expand(data[0]); - // this.showItem(data[0]); - // }); } - showItem(el) { + ngOnDestroy() { + this.folderTreeSubs.unsubscribe(); + this.bucketDataService._bucketData.next([]); + } + + private showItem(el) { if (el) { this.treeControl.expand(el); this.selectedFolder = el; @@ -92,7 +101,7 @@ export class FolderTreeComponent implements OnInit { } } - getPath(el) { + private getPath(el) { if (el) { if (this.path.length === 0) { this.path.unshift(el.item); @@ -105,7 +114,17 @@ export class FolderTreeComponent implements OnInit { } } - descendantsAllSelected(node: TodoItemFlatNode): boolean { + private expandAllParents(el) { + if (el) { + this.treeControl.expand(el); + if (this.getParentNode(el) !== null) { + this.expandAllParents(this.getParentNode(el)); + } + } + } + + private descendantsAllSelected(node: TodoItemFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); const descAllSelected = descendants.every(child => this.checklistSelection.isSelected(child) @@ -113,13 +132,14 @@ export class FolderTreeComponent implements OnInit { return descAllSelected; } - descendantsPartiallySelected(node: TodoItemFlatNode): boolean { + private descendantsPartiallySelected(node: TodoItemFlatNode): boolean { + const descendants = this.treeControl.getDescendants(node); const result = descendants.some(child => this.checklistSelection.isSelected(child)); return result && !this.descendantsAllSelected(node); } - todoItemSelectionToggle(node: TodoItemFlatNode): void { + private todoItemSelectionToggle(node: TodoItemFlatNode): void { this.checklistSelection.toggle(node); const descendants = this.treeControl.getDescendants(node); this.checklistSelection.isSelected(node) @@ -133,12 +153,12 @@ export class FolderTreeComponent implements OnInit { this.checkAllParentsSelection(node); } - todoLeafItemSelectionToggle(node: TodoItemFlatNode): void { + private todoLeafItemSelectionToggle(node: TodoItemFlatNode): void { this.checklistSelection.toggle(node); this.checkAllParentsSelection(node); } - checkAllParentsSelection(node: TodoItemFlatNode): void { + private checkAllParentsSelection(node: TodoItemFlatNode): void { let parent: TodoItemFlatNode | null = this.getParentNode(node); while (parent) { this.checkRootNodeSelection(parent); @@ -146,7 +166,8 @@ export class FolderTreeComponent implements OnInit { } } - checkRootNodeSelection(node: TodoItemFlatNode): void { + + private checkRootNodeSelection(node: TodoItemFlatNode): void { const nodeSelected = this.checklistSelection.isSelected(node); const descendants = this.treeControl.getDescendants(node); const descAllSelected = descendants.every(child => @@ -159,9 +180,8 @@ export class FolderTreeComponent implements OnInit { } } - getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null { + private getParentNode(node: TodoItemFlatNode): TodoItemFlatNode | null { const currentLevel = this.getLevel(node); - if (currentLevel < 1) { return null; } @@ -177,15 +197,16 @@ export class FolderTreeComponent implements OnInit { } return null; } - - addNewItem(node: TodoItemFlatNode, file, isFile, path) { + + private addNewItem(node: TodoItemFlatNode, file, isFile, path) { const parentNode = this.flatNodeMap.get(node); - this.bucketBrowserService.insertItem(parentNode!, file, isFile); + this.bucketDataService.insertItem(parentNode!, file, isFile); this.treeControl.expand(node); } - saveNode(node: TodoItemFlatNode, itemValue: string) { + private saveNode(node: TodoItemFlatNode, itemValue: string) { const nestedNode = this.flatNodeMap.get(node); - this.bucketBrowserService.updateItem(nestedNode!, itemValue); + this.bucketDataService.updateItem(nestedNode!, itemValue); + } } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html index 817ca22..9e669b6 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.html @@ -60,14 +60,14 @@ class="strong">{{ notebook.password }}</span></p> <p class="m-top-30">{{ DICTIONARY[PROVIDER].personal_storage }}: </p> - <div class="links_block" (click)="bucketBrowser('project', notebook.endpoint)"> + <div class="links_block" (click)="bucketBrowser(notebook.bucket_name, notebook.endpoint)"> <p *ngIf="DICTIONARY[PROVIDER].cloud_provider === 'azure' && notebook.account_name">{{ DICTIONARY[PROVIDER].account }} <span class="bucket-info">{{ notebook.account_name}}</span></p> <p *ngIf="notebook.bucket_name">{{ DICTIONARY[PROVIDER].container }} <span class="bucket-info">{{ notebook.bucket_name }}</span></p> </div> <p>Shared endpoint bucket: </p> - <div class="links_block" (click)="bucketBrowser('endpoint', notebook.endpoint)"> + <div class="links_block" (click)="bucketBrowser(notebook.shared_bucket_name, notebook.endpoint)"> <p *ngIf="DICTIONARY[PROVIDER].cloud_provider === 'azure' && notebook.shared_account_name">{{ DICTIONARY[PROVIDER].account }} <span class="bucket-info">{{ notebook.shared_account_name}}</span></p> <p *ngIf="notebook.shared_bucket_name">{{ DICTIONARY[PROVIDER].container }} diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts index ca264b9..ac5b6ba 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/detail-dialog/detail-dialog.component.ts @@ -123,8 +123,8 @@ export class DetailDialogComponent implements OnInit { : null; } - public bucketBrowser(type, endpoint): void { - this.dialog.open(BucketBrowserComponent, { data: {type: type, endpoint: endpoint}, panelClass: 'modal-fullscreen' }) + public bucketBrowser(bucketName, endpoint): void { + this.dialog.open(BucketBrowserComponent, { data: {bucket: bucketName, endpoint: endpoint}, panelClass: 'modal-fullscreen' }) .afterClosed().subscribe(); } } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts index d2d5f02..c87d12c 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/resources.module.ts @@ -30,6 +30,8 @@ import { ConfirmDeleteAccountDialog } from './manage-ungit/manage-ungit.componen import {BucketBrowserComponent} from './bucket-browser/bucket-browser.component'; import {FolderTreeComponent} from './bucket-browser/folder-tree/folder-tree.component'; import {MatTreeModule} from '@angular/material/tree'; +import {BucketDataService} from './bucket-browser/bucket-data.service'; + @NgModule({ imports: [ @@ -49,6 +51,7 @@ import {MatTreeModule} from '@angular/material/tree'; FolderTreeComponent ], entryComponents: [ManageUngitComponent, ConfirmDeleteAccountDialog, BucketBrowserComponent, FolderTreeComponent], + providers: [BucketDataService], exports: [ResourcesComponent] }) export class ResourcesModule { } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@dlab.apache.org For additional commands, e-mail: commits-h...@dlab.apache.org