diff --git a/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.html b/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.html index b4e942d..1deb903 100644 --- a/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.html +++ b/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.html @@ -106,7 +106,7 @@

Create New User

mat-raised-button color="primary" (click)="createUser()" - [disabled]="newUserForm.invalid" + [disabled]="newUserForm.invalid || !costEstimation" > Create User diff --git a/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.ts b/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.ts index d30f97d..abe8e8c 100644 --- a/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.ts +++ b/webgui/webapp/src/app/pages/admin-page/components/admin-create-user-dialog/admin-create-user-dialog.component.ts @@ -33,6 +33,7 @@ import { MatSnackBar } from '@angular/material/snack-bar'; import { AwsService } from 'src/app/services/aws.service'; import { DportalService } from 'src/app/services/dportal.service'; import { gigabytesToBytes } from 'src/app/utils/file'; +import { UserQuotaService } from 'src/app/services/userquota.service'; @Component({ selector: 'app-admin-create-user-dialog', @@ -71,7 +72,7 @@ export class AdminCreateUserComponent implements OnInit { private adminServ: AdminService, private sb: MatSnackBar, private aws: AwsService, - private dp: DportalService, + private uq: UserQuotaService, ) { this.newUserForm = this.fb.group({ firstName: ['', Validators.required], @@ -165,7 +166,7 @@ export class AdminCreateUserComponent implements OnInit { } addUserQuota(sub: string): void { - this.dp + this.uq .upsertUserQuota(sub, this.costEstimation, { quotaSize: gigabytesToBytes(this.newUserForm.value.quotaSize), quotaQueryCount: this.newUserForm.value.quotaQueryCount, diff --git a/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.html b/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.html index b625e31..289f31b 100644 --- a/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.html +++ b/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.html @@ -122,7 +122,7 @@

Update {{ data.name }}

mat-raised-button color="primary" (click)="done()" - [disabled]="form.invalid" + [disabled]="form.invalid || !costEstimation" > Update diff --git a/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.ts b/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.ts index 8949f3f..032c4f4 100644 --- a/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.ts +++ b/webgui/webapp/src/app/pages/admin-page/components/admin-user-click-dialog/admin-user-click-dialog.component.ts @@ -31,6 +31,7 @@ import { MatInputModule } from '@angular/material/input'; import { DportalService } from 'src/app/services/dportal.service'; import { AwsService } from 'src/app/services/aws.service'; import { bytesToGigabytes, gigabytesToBytes } from 'src/app/utils/file'; +import { UserQuotaService } from 'src/app/services/userquota.service'; @Component({ selector: 'app-admin-user-click-dialog', @@ -67,7 +68,7 @@ export class AdminUserClickDialogComponent implements OnInit { public dialogRef: MatDialogRef, private fb: FormBuilder, private as: AdminService, - private dp: DportalService, + private uq: UserQuotaService, private aws: AwsService, private dg: MatDialog, @Inject(MAT_DIALOG_DATA) public data: any, @@ -108,7 +109,7 @@ export class AdminUserClickDialogComponent implements OnInit { getUserGroups() { this.loading = true; // Define both observables - const userQuota$ = this.dp + const userQuota$ = this.uq .getUserQuota(this.data.sub) .pipe(catchError(() => of(null))); @@ -192,7 +193,7 @@ export class AdminUserClickDialogComponent implements OnInit { } updateQuota() { - return this.dp + return this.uq .upsertUserQuota(this.data.sub, this.costEstimation, { quotaSize: gigabytesToBytes(this.form.value.quotaSize), quotaQueryCount: this.form.value.quotaQueryCount, diff --git a/webgui/webapp/src/app/pages/portal-page/dportal-page/user-section/user-file-list/user-file-list.component.ts b/webgui/webapp/src/app/pages/portal-page/dportal-page/user-section/user-file-list/user-file-list.component.ts index 4506f2e..b668c92 100644 --- a/webgui/webapp/src/app/pages/portal-page/dportal-page/user-section/user-file-list/user-file-list.component.ts +++ b/webgui/webapp/src/app/pages/portal-page/dportal-page/user-section/user-file-list/user-file-list.component.ts @@ -5,11 +5,10 @@ import { MatIconModule } from '@angular/material/icon'; import { Storage } from 'aws-amplify'; import { MatTooltipModule } from '@angular/material/tooltip'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; -import { DUMMY_DATA_STORAGE } from 'src/app/utils/data'; import { AuthService } from 'src/app/services/auth.service'; import { catchError, filter, firstValueFrom, of } from 'rxjs'; import { DportalService } from 'src/app/services/dportal.service'; -import { formatBytes, getTotalSize } from 'src/app/utils/file'; +import { formatBytes, getTotalStorageSize } from 'src/app/utils/file'; import { UserQuotaService } from 'src/app/services/userquota.service'; @Component({ @@ -51,28 +50,18 @@ export class UserFileListComponent implements OnInit { } generateTotalSize(files: any[]) { - const bytesTotal = getTotalSize(files); + const bytesTotal = getTotalStorageSize(files); this.totalSize = bytesTotal; this.totalSizeFormatted = formatBytes(bytesTotal); } - dummyList() { - this.myFiles = DUMMY_DATA_STORAGE.results; - this.generateTotalSize(this.myFiles); - } - async list() { - // this.dummyList(); - const res = await Storage.list(``, { pageSize: 'ALL', level: 'private', }); - console.log('res storage list (res)', res); - console.log('res storage results (res.results)', res.results); - // TODO: Update this.myFiles with the results from the Storage.list call this.myFiles = res.results; this.generateTotalSize(this.myFiles); } diff --git a/webgui/webapp/src/app/pages/portal-page/query-page/components/query-result-viewer-container/query-result-viewer-container.component.ts b/webgui/webapp/src/app/pages/portal-page/query-page/components/query-result-viewer-container/query-result-viewer-container.component.ts index acf38f2..8c69125 100644 --- a/webgui/webapp/src/app/pages/portal-page/query-page/components/query-result-viewer-container/query-result-viewer-container.component.ts +++ b/webgui/webapp/src/app/pages/portal-page/query-page/components/query-result-viewer-container/query-result-viewer-container.component.ts @@ -11,9 +11,12 @@ import { MatCardModule } from '@angular/material/card'; import { DportalService } from 'src/app/services/dportal.service'; import { MatDialog, MatDialogModule } from '@angular/material/dialog'; import { SpinnerService } from 'src/app/services/spinner.service'; -import { catchError, from, of } from 'rxjs'; +import { catchError, filter, firstValueFrom, from, of, switchMap } from 'rxjs'; import { MatSnackBar, MatSnackBarModule } from '@angular/material/snack-bar'; import { Storage } from 'aws-amplify'; +import { getTotalStorageSize } from 'src/app/utils/file'; +import { UserQuotaService } from 'src/app/services/userquota.service'; +import { AuthService } from 'src/app/services/auth.service'; @Component({ selector: 'app-query-result-viewer-container', @@ -51,6 +54,7 @@ export class QueryResultViewerContainerComponent implements OnChanges { private dg: MatDialog, private ss: SpinnerService, private sb: MatSnackBar, + private uq: UserQuotaService, ) {} ngOnChanges(): void { @@ -69,6 +73,7 @@ export class QueryResultViewerContainerComponent implements OnChanges { type: 'text/json;charset=utf-8;', }); const url = URL.createObjectURL(blob); + const a = document.createElement('a'); a.href = url; a.download = 'data.json'; @@ -77,7 +82,52 @@ export class QueryResultViewerContainerComponent implements OnChanges { document.body.removeChild(a); } + // Calculate total size from storage and current query result + async totalStorage(queryResults: any) { + // Get files in the storage + const res = await Storage.list(``, { + pageSize: 'ALL', + level: 'private', + }); + + // Get total size from storage + const bytesTotal = getTotalStorageSize(res.results); + + // Get size from current query result + const blob = new Blob([JSON.stringify(queryResults, null, 2)], { + type: 'text/json;charset=utf-8;', + }); + + return bytesTotal + blob.size; + } + + updateUserQuota(userQuota: any, currentTotalSize: number) { + this.uq + .upsertUserQuota(userQuota.userSub, userQuota.costEstimation, { + quotaSize: userQuota.quotaSize, + quotaQueryCount: userQuota.quotaQueryCount, + usageSize: currentTotalSize, + usageCount: userQuota.usageCount, + }) + .pipe(catchError(() => of(null))); + } + async save(content: any) { + const userQuota = await firstValueFrom(this.uq.getCurrentUsage()); + const currentTotalSize = await this.totalStorage(content); + + // Check if the current total size is greater than the user's quota size + if (currentTotalSize >= userQuota.quotaSize) { + this.sb.open( + 'Cannot Save Query because Quota Limit reached. Please contact administrator to increase your quota.', + 'Okay', + { + duration: 60000, + }, + ); + return; + } + const { TextInputDialogComponent } = await import( '../../../../../components/text-input-dialog/text-input-dialog.component' ); @@ -91,6 +141,7 @@ export class QueryResultViewerContainerComponent implements OnChanges { placeholder: 'My query results', }, }); + dialog.afterClosed().subscribe((name) => { if (name) { this.ss.start(); @@ -105,6 +156,8 @@ export class QueryResultViewerContainerComponent implements OnChanges { if (!res) { this.sb.open('Saving failed', 'Okay', { duration: 60000 }); } + + this.updateUserQuota(userQuota, currentTotalSize); this.ss.end(); }); } diff --git a/webgui/webapp/src/app/pages/portal-page/query-page/components/query-tab/query-tab.component.ts b/webgui/webapp/src/app/pages/portal-page/query-page/components/query-tab/query-tab.component.ts index 226ffc2..a4ed13b 100644 --- a/webgui/webapp/src/app/pages/portal-page/query-page/components/query-tab/query-tab.component.ts +++ b/webgui/webapp/src/app/pages/portal-page/query-page/components/query-tab/query-tab.component.ts @@ -317,7 +317,7 @@ export class QueryTabComponent implements OnInit, AfterViewInit, OnDestroy { ); if (usageCount >= quotaQueryCount) { - this.sb.open('Run Query is reach Quota Limit.', 'Okay', { + this.sb.open('Run Query is reach quota limit.', 'Okay', { duration: 60000, }); this.ss.end(); @@ -367,7 +367,7 @@ export class QueryTabComponent implements OnInit, AfterViewInit, OnDestroy { this.endpoint = endpoint; this.scope = form.customReturn ? form.return : form.scope; - this.dps.incrementUsageCount(userSub).subscribe(() => { + this.uq.incrementUsageCount(userSub).subscribe(() => { console.log('usage count incremented'); }); } else { diff --git a/webgui/webapp/src/app/services/dportal.service.ts b/webgui/webapp/src/app/services/dportal.service.ts index d88bf86..b841309 100644 --- a/webgui/webapp/src/app/services/dportal.service.ts +++ b/webgui/webapp/src/app/services/dportal.service.ts @@ -328,36 +328,4 @@ export class DportalService { ), ); } - - getUserQuota(id: string) { - console.log('get user quota'); - return from( - API.get(environment.api_endpoint_sbeacon.name, `dportal/quota/${id}`, {}), - ); - } - - upsertUserQuota(id: string, costEstimation: number | null, usage: any) { - console.log('upsert user quota'); - return from( - API.post(environment.api_endpoint_sbeacon.name, 'dportal/quota', { - body: { - IdentityUser: id, - CostEstimation: costEstimation, - Usage: usage, - }, - }), - ); - } - - incrementUsageCount(id: string) { - debugger; - console.log('incrementUsageCount'); - return from( - API.post( - environment.api_endpoint_sbeacon.name, - `dportal/quota/${id}/increment_usagecount`, - {}, - ), - ); - } } diff --git a/webgui/webapp/src/app/services/userquota.service.ts b/webgui/webapp/src/app/services/userquota.service.ts index 6ad32eb..041be9f 100644 --- a/webgui/webapp/src/app/services/userquota.service.ts +++ b/webgui/webapp/src/app/services/userquota.service.ts @@ -31,7 +31,6 @@ export class UserQuotaService { } incrementUsageCount(id: string) { - debugger; console.log('incrementUsageCount'); return from( API.post( diff --git a/webgui/webapp/src/app/utils/data.ts b/webgui/webapp/src/app/utils/data.ts deleted file mode 100644 index 526dbcd..0000000 --- a/webgui/webapp/src/app/utils/data.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const DUMMY_DATA_STORAGE = { - results: [ - { - key: 'saved-queries/Query-Save2.json', - eTag: '"9a2d229e94e86395136d288e49705b33"', - lastModified: '2024-12-06T07:13:31.000Z', - size: 964, - }, - { - key: 'saved-queries/Querysave-1.json', - eTag: '"0b76dbb1ff87c34fe91695b32900f369"', - lastModified: '2024-12-06T07:12:03.000Z', - size: 961, - }, - { - key: 'vcf_diabetes-1.vcf.gz.tbi', - eTag: '"d5c3e39a6ce19bfdb0e072fba03f1e61"', - lastModified: '2024-12-06T07:16:20.000Z', - size: 106, - }, - ], - hasNextToken: false, -}; diff --git a/webgui/webapp/src/app/utils/file.ts b/webgui/webapp/src/app/utils/file.ts index c4948c1..efb35c0 100644 --- a/webgui/webapp/src/app/utils/file.ts +++ b/webgui/webapp/src/app/utils/file.ts @@ -63,6 +63,6 @@ export function bytesToGigabytes(bytes: number): number { * getTotalSize(dataStorage); // 123456789 * ``` */ -export function getTotalSize(dataStorage: any): number { +export function getTotalStorageSize(dataStorage: any): number { return dataStorage.reduce((total: number, item: any) => total + item.size, 0); }