This is an automated email from the ASF dual-hosted git repository. hshpak pushed a commit to branch feat/DATALAB-2996/sharing-confirmation in repository https://gitbox.apache.org/repos/asf/incubator-datalab.git
commit d1ddd1b1e38aa03063c8ca825121edd4bdcce2b3 Author: Hennadii_Shpak <[email protected]> AuthorDate: Fri Sep 9 13:32:13 2022 +0300 DATALAB-2996 implemented sharing confirmation --- .../services/applicationServiceFacade.service.ts | 6 +++ .../app/core/services/user-images-page.service.ts | 16 +++++- .../src/app/core/services/userResource.service.ts | 1 - .../image-action-dialog.component.html | 7 ++- .../image-action-dialog.component.ts | 3 +- .../share-dialog/share-dialog.component.html | 17 +++++-- .../share-dialog/share-dialog.component.ts | 58 ++++++++++++++++++---- .../terminate-dialog.component.html | 2 +- .../src/app/resources/images/images.component.ts | 22 +++++--- .../src/app/resources/images/images.config.ts | 2 +- .../src/app/resources/images/images.model.ts | 5 +- .../src/app/resources/images/images.service.ts | 12 +++-- 12 files changed, 118 insertions(+), 33 deletions(-) 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 c4715c6ae..69cd6e607 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 @@ -460,6 +460,12 @@ export class ApplicationServiceFacade { data, { responseType: 'text', observe: 'response' }); } + public buildGetImageShareInfo(data: string): Observable<any> { + return this.buildRequest(HTTPMethod.GET, + this.requestRegistry.Item(ApplicationServiceFacade.IMAGE) + data, + null); + } + public buildGetExploratorySchedule(data): Observable<any> { return this.buildRequest(HTTPMethod.GET, this.requestRegistry.Item(ApplicationServiceFacade.SCHEDULER), diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts index 49d6ace10..5811202da 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/user-images-page.service.ts @@ -19,7 +19,7 @@ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; -import { catchError, map } from 'rxjs/operators'; +import { catchError } from 'rxjs/operators'; import { ErrorUtils } from '../util'; import { ApplicationServiceFacade } from './applicationServiceFacade.service'; @@ -29,6 +29,7 @@ import { ProjectImagesInfo, ImageParams } from '../../resources/images'; +import { UserData } from '../../resources/exploratory/image-action-dialog/image-action.model'; @Injectable() export class UserImagesPageService { @@ -64,6 +65,17 @@ export class UserImagesPageService { return this.applicationServiceFacade .buildDeleteImage(JSON.stringify(url)) .pipe( - catchError(ErrorUtils.handleServiceError)); + catchError(ErrorUtils.handleServiceError) + ); + } + + getImageShareInfo(image: ImageParams): Observable<UserData[]> { + const { imageName, projectName, endpoint} = image; + const url = `/sharing_info/${imageName}/${projectName}/${endpoint}/`; + return this.applicationServiceFacade + .buildGetImageShareInfo(url) + .pipe( + catchError(ErrorUtils.handleServiceError) + ); } } diff --git a/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts b/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts index 6f5fe08ff..406270b90 100644 --- a/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/core/services/userResource.service.ts @@ -184,7 +184,6 @@ export class UserResourceService { } public createAMI(data): Observable<any> { - const body = JSON.stringify(data); return this.applicationServiceFacade .buildCreateAMI(data) .pipe( diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.html index 297a95bb7..238361994 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.html @@ -19,7 +19,10 @@ <div id="dialog-box"> <header class="dialog-header"> - <h4 class="modal-title">{{data.title}}<span *ngIf="data.actionType === actionType.share">: {{data.imageName}}</span></h4> + <h4 class="modal-title">{{data.title}}<span + *ngIf="data.actionType === actionType.share">: {{data.image.name}} + </span> + </h4> <button type="button" class="close" (click)="dialogRef.close()">×</button> </header> <section class="content"> @@ -33,7 +36,7 @@ [disabled]="isApplyBtnDisabled" type="button" class="butt butt-success mat-raised-button" - (click)="dialogRef.close(true)"> + (click)="dialogRef.close(responseObj)"> {{confirmBtnName}} </button> </div> diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.ts index 2b7e46a1a..79f7049b3 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/image-action-dialog.component.ts @@ -20,7 +20,7 @@ import { Component, Inject, Input, OnInit } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { ImageActions, ImageActionModalData } from '../../images'; -import { DialogWindowTabConfig } from './image-action.model'; +import { DialogWindowTabConfig, UserData } from './image-action.model'; @Component({ selector: 'datalab-image-action-dialog', @@ -30,6 +30,7 @@ import { DialogWindowTabConfig } from './image-action.model'; export class ImageActionDialogComponent implements OnInit { @Input() activeTabConfig: DialogWindowTabConfig; @Input() isApplyBtnDisabled: Boolean; + @Input() responseObj: UserData[] = []; readonly actionType: typeof ImageActions = ImageActions; diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.html index 8b7f10eb1..3b527305c 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.html @@ -17,7 +17,14 @@ ~ under the License. --> -<datalab-image-action-dialog [activeTabConfig]="activeTabConfig" [isApplyBtnDisabled]="isApplyBtnDisabled"> +<datalab-image-action-dialog + [activeTabConfig]="activeTabConfig" + [responseObj]="responseObj" + [isApplyBtnDisabled]="isApplyBtnDisabled" +> + + <ng-container *ngIf="($getUserListData | async)"></ng-container> + <div datalab-share-dialog class="wrapper"> <ul class="title__list"> <li @@ -91,8 +98,8 @@ </div> <div class="user-list__wrapper scrolling"> <ul class="user-list"> - <li *ngFor="let entity of temporaryUserList" class="user-list__item"> - <datalab-share-user-data (removeUserData)="onRemoveUserData($event, tabsName.shareImage)" [userData]="entity"></datalab-share-user-data> + <li *ngFor="let entity of temporaryUserDataList" class="user-list__item"> + <datalab-share-user-data (removeUserData)="onRemoveUserData($event)" [userData]="entity"></datalab-share-user-data> </li> </ul> </div> @@ -101,8 +108,8 @@ <div *ngIf="activeTabConfig.shareWith" class="share-with__wrapper"> <div class="user-list__wrapper scrolling"> <ul class="user-list"> - <li *ngFor="let entity of userList" class="user-list__item"> - <datalab-share-user-data (removeUserData)="onRemoveUserData($event, tabsName.shareWith)" [userData]="entity"></datalab-share-user-data> + <li *ngFor="let entity of userDataList" class="user-list__item"> + <datalab-share-user-data (removeUserData)="onRemoveUserData($event)" [userData]="entity"></datalab-share-user-data> </li> </ul> </div> diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.ts index 50a194a8c..05fb44872 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/share-dialog/share-dialog.component.ts @@ -17,10 +17,15 @@ * under the License. */ -import { ChangeDetectionStrategy, Component, ViewChild } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, OnInit, ViewChild } from '@angular/core'; import { SharePlaceholder, TabName, UserDataTypeConfig } from '../image-action.config'; import { DialogWindowTabConfig, UserData, UserDataType } from '../image-action.model'; import { NgModel } from '@angular/forms'; +import { ImagesService } from '../../../images/images.service'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ImageActionModalData } from '../../../images'; +import { tap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; @Component({ selector: 'datalab-share-dialog', @@ -28,15 +33,15 @@ import { NgModel } from '@angular/forms'; styleUrls: ['./share-dialog.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) -export class ShareDialogComponent { +export class ShareDialogComponent implements OnInit { @ViewChild('searchUser') searchUser: NgModel; readonly placeholder: typeof SharePlaceholder = SharePlaceholder; readonly tabsName: typeof TabName = TabName; readonly userDataTypeConfig: typeof UserDataTypeConfig = UserDataTypeConfig; - userList: UserData[] = []; - temporaryUserList: UserData[] = []; + userDataList: UserData[] = []; + temporaryUserDataList: UserData[] = []; userNameOrGroup: UserDataType; activeTabConfig: DialogWindowTabConfig = { shareImage: true, @@ -44,6 +49,18 @@ export class ShareDialogComponent { }; searchInput = ''; + $getUserListData: Observable<UserData[]>; + + constructor( + private imagesService: ImagesService, + @Inject(MAT_DIALOG_DATA) public data: ImageActionModalData, + ) { + } + + ngOnInit(): void { + this.getSharingUserList(); + } + onAddUser(): void { if (!this.searchInput) { return; @@ -52,21 +69,44 @@ export class ShareDialogComponent { value: this.searchInput.trim(), type: this.userNameOrGroup }; - this.temporaryUserList = [...this.temporaryUserList, newUserEntity]; + this.temporaryUserDataList = [...this.temporaryUserDataList, newUserEntity] + .sort((a, b) => a.value < b.value ? 1 : -1) + .sort((a, b) => a.type > b.type ? 1 : -1); this.searchInput = ''; } + aaa() { + console.log(this.searchUser); + + } + onTabTitle(tabName: keyof DialogWindowTabConfig): void { Object.keys(this.activeTabConfig).forEach(item => this.activeTabConfig[item] = false); this.activeTabConfig = {...this.activeTabConfig, [tabName]: true}; } - onRemoveUserData(userName: string, tabName: TabName): void { - this.temporaryUserList = this.temporaryUserList.filter(({value}) => value !== userName); + onRemoveUserData(userName: string): void { + this.temporaryUserDataList = this.temporaryUserDataList.filter(({value}) => value !== userName); + } + + private getSharingUserList(): void { + const { name, project, endpoint} = this.data.image; + const imageParams = { + imageName: name, + projectName: project, + endpoint + }; + this.$getUserListData = this.imagesService.getImageShareInfo(imageParams).pipe( + tap(userListData => this.userDataList = userListData) + ); + } + + get responseObj(): UserData[] { + return [...this.temporaryUserDataList, ...this.userDataList]; } get isApplyBtnDisabled(): boolean { - return this.searchInput.length > 25 || !Boolean(this.temporaryUserList.length); + return this.searchInput.length > 25 || !Boolean(this.temporaryUserDataList.length); } get isAddUserDataBtnDisabled(): boolean { @@ -80,7 +120,7 @@ export class ShareDialogComponent { get isUserInputEmpty(): boolean { return this.searchUser?.control.touched && !Boolean(this.searchInput.length) - && !Boolean(this.temporaryUserList.length); + && !Boolean(this.temporaryUserDataList.length); } get isLongInputMessage() { diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/terminate-dialog/terminate-dialog.component.html b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/terminate-dialog/terminate-dialog.component.html index 532f4e2d0..e7328d1f5 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/terminate-dialog/terminate-dialog.component.html +++ b/services/self-service/src/main/resources/webapp/src/app/resources/exploratory/image-action-dialog/terminate-dialog/terminate-dialog.component.html @@ -25,7 +25,7 @@ <div class="status">Further status</div> </div> <div class="scrolling-content scrolling terminate-image-list__wrapper"> - <div class="image-name ellipsis">{{data.imageName}}</div> + <div class="image-name ellipsis">{{data.image.name}}</div> <div class="status terminated">Terminated</div> </div> <p *ngIf="data.isShared" class="shared-warning">!The image is shared with other users.</p> diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts index dca4feafb..b4ec1677a 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.component.ts @@ -19,8 +19,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatDialog } from '@angular/material/dialog'; -import { EMPTY, Observable } from 'rxjs'; -import { map, switchMap, tap } from 'rxjs/operators'; +import { EMPTY, Observable, of } from 'rxjs'; +import { catchError, map, switchMap, tap } from 'rxjs/operators'; import { ToastrService } from 'ngx-toastr'; @@ -31,7 +31,7 @@ import { ImageActionType, ImageFilterFormDropdownData, ImageFilterFormValue, - ImageModel, + ImageModel, ImageParams, ProjectModel } from './images.model'; import { @@ -49,12 +49,10 @@ import { ImageActions, Toaster_Message, } from './images.config'; -import { ImageActionDialogComponent } from '../exploratory/image-action-dialog/image-action-dialog.component'; import { ImagesService } from './images.service'; import { ProgressBarService } from '../../core/services/progress-bar.service'; import { ImageDetailDialogComponent } from '../exploratory/image-detail-dialog/image-detail-dialog.component'; import { ActivatedRoute } from '@angular/router'; -import { TerminateDialogComponent } from '../exploratory/image-action-dialog/terminate-dialog/terminate-dialog.component'; @Component({ selector: 'datalab-images', @@ -162,7 +160,7 @@ export class ImagesComponent implements OnInit, OnDestroy { } onActionClick(image: ImageModel, actionType: ImageActionType): void { - const imageInfo = this.imagesService.createImageRequestInfo(image); + let imageInfo: ImageParams; const data = this.imagesService.createActionDialogConfig(image, actionType); const requestCallback = this.imagesService.getRequestByAction(actionType).bind(this.imagesService); const component = this.imagesService.getComponentByAction(actionType); @@ -172,13 +170,17 @@ export class ImagesComponent implements OnInit, OnDestroy { panelClass: 'modal-sm' }).afterClosed() .pipe( + tap(userDataList => { + imageInfo = this.imagesService.createImageRequestInfo(image, userDataList); + }), switchMap((confirm) => { if (confirm) { return requestCallback(imageInfo, actionType); } return EMPTY; }), - tap(() => this.callActionHelpers(actionType, this.callToasterShareSuccess)) + tap(() => this.callActionHelpers(actionType, this.callToasterShareSuccess)), + catchError(() => of(this.callToasterShareError(actionType))) ) .subscribe(); } @@ -239,6 +241,12 @@ export class ImagesComponent implements OnInit, OnDestroy { } } + private callToasterShareError(actionType: ImageActionType): void { + if (actionType === ImageActions.share) { + this.toastr.error('Something went wrong. Please try again.', 'Oops!'); + } + } + private checkAuthorize(): void { this.applicationSecurityService.isLoggedIn().subscribe(() => { this.getEnvironmentHealthStatus(); diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts index ef7cb6289..2bf2e0f39 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.config.ts @@ -50,7 +50,7 @@ export enum Localstorage_Key { } export enum Toaster_Message { - successShare = 'The image has been shared with all current Regular Users on the project!', + successShare = 'The image has been successfully shared.', successTerminate = 'The image has been terminated' } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts index 09b32cce8..63f95908c 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.model.ts @@ -17,6 +17,8 @@ * under the License. */ +import { UserData } from '../exploratory/image-action-dialog/image-action.model'; + export interface ProjectImagesInfo { filterData: ImageFilterFormDropdownData; imageFilter: ImageFilterFormValue; @@ -58,12 +60,13 @@ export interface ImageParams { imageName: string; projectName: string; endpoint: string; + sharedWith?: UserData[]; } export interface ImageActionModalData { actionType: ImageActionType; title: string; - imageName: string; + image: ImageModel; isShared?: boolean; } diff --git a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts index a3dfee632..469086713 100644 --- a/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts +++ b/services/self-service/src/main/resources/webapp/src/app/resources/images/images.service.ts @@ -19,6 +19,7 @@ import { ChangedColumnStartValue, FilterFormInitialValue, ModalTitle, SharedStat import { ShareDialogComponent } from '../exploratory/image-action-dialog/share-dialog/share-dialog.component'; import { TerminateDialogComponent } from '../exploratory/image-action-dialog/terminate-dialog/terminate-dialog.component'; import { ComponentType } from 'ngx-toastr'; +import { UserData } from '../exploratory/image-action-dialog/image-action.model'; @Injectable({ providedIn: 'root' @@ -81,6 +82,10 @@ export class ImagesService { ); } + getImageShareInfo(imageInfo: ImageParams): Observable<UserData[]> { + return this.userImagesPageService.getImageShareInfo(imageInfo); + } + getActiveProject(projectName: string): void { const projectList = this.$$cashedProjectList.getValue(); if (!projectName) { @@ -176,12 +181,13 @@ export class ImagesService { this.$$isImageListFiltered.next(isImageListFiltered); } - createImageRequestInfo(image: ImageModel): ImageParams { + createImageRequestInfo(image: ImageModel, userDataList: UserData[]): ImageParams { const { name, project, endpoint } = image; return { imageName: name, projectName: project, - endpoint: endpoint + endpoint: endpoint, + sharedWith: userDataList }; } @@ -193,7 +199,7 @@ export class ImagesService { return { title: modalTitle[actionType], actionType, - imageName: image.name, + image, isShared: this.isImageShared(image) }; } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
