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);
}