From 554136cdcd4acb939adff140af9d05931eda867e Mon Sep 17 00:00:00 2001 From: Thomas Kaul <4159106+dtslvr@users.noreply.github.com> Date: Sun, 30 Jun 2024 09:21:04 +0200 Subject: [PATCH] Feature/bulk deletion for asset profiles (#3531) * Add support for bulk deletion of asset profiles * Update changelog --- CHANGELOG.md | 4 +++ .../admin-market-data.component.ts | 19 ++++++++++-- .../admin-market-data/admin-market-data.html | 30 +++++++++++++++++- .../admin-market-data.module.ts | 2 ++ .../admin-market-data.service.ts | 31 +++++++++++++++++-- .../asset-profile-dialog.component.ts | 2 +- 6 files changed, 82 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80b0dca84..096a8e20e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +### Added + +- Added support for bulk deletion of asset profiles from the market data table in the admin control panel + ### Changed - Added support for derived currencies in the currency validation diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts index 6051c94dc..add0e998f 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.component.ts @@ -10,6 +10,7 @@ import { Filter, UniqueAsset, User } from '@ghostfolio/common/interfaces'; import { AdminMarketDataItem } from '@ghostfolio/common/interfaces/admin-market-data.interface'; import { translate } from '@ghostfolio/ui/i18n'; +import { SelectionModel } from '@angular/cdk/collections'; import { AfterViewInit, ChangeDetectionStrategy, @@ -97,6 +98,7 @@ export class AdminMarketDataComponent public defaultDateFormat: string; public deviceType: string; public displayedColumns = [ + 'select', 'nameWithSymbol', 'dataSource', 'assetClass', @@ -115,6 +117,7 @@ export class AdminMarketDataComponent public isUUID = isUUID; public placeholder = ''; public pageSize = DEFAULT_PAGE_SIZE; + public selection: SelectionModel>; public totalItems = 0; public user: User; @@ -188,6 +191,8 @@ export class AdminMarketDataComponent this.benchmarks = benchmarks; this.deviceType = this.deviceService.getDeviceInfo().deviceType; + + this.selection = new SelectionModel(true); } public onChangePage(page: PageEvent) { @@ -198,8 +203,16 @@ export class AdminMarketDataComponent }); } - public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { - this.adminMarketDataService.deleteProfileData({ dataSource, symbol }); + public onDeleteAssetProfile({ dataSource, symbol }: UniqueAsset) { + this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol }); + } + + public onDeleteAssetProfiles() { + this.adminMarketDataService.deleteAssetProfiles( + this.selection.selected.map(({ dataSource, symbol }) => { + return { dataSource, symbol }; + }) + ); } public onGather7Days() { @@ -286,6 +299,8 @@ export class AdminMarketDataComponent this.placeholder = this.activeFilters.length <= 0 ? $localize`Filter by...` : ''; + this.selection.clear(); + this.adminService .fetchAdminMarketData({ sortColumn, diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.html b/apps/client/src/app/components/admin-market-data/admin-market-data.html index 0606b136a..5a7312026 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.html +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.html @@ -20,6 +20,27 @@ matSortDirection="asc" [dataSource]="dataSource" > + + + + @if ( + !( + element.activitiesCount !== 0 || + element.isBenchmark || + element.symbol.startsWith(ghostfolioScraperApiSymbolPrefix) + ) + ) { + + + } + + + Symbol @@ -152,6 +173,13 @@ + @@ -186,7 +214,7 @@ element.symbol.startsWith(ghostfolioScraperApiSymbolPrefix) " (click)=" - onDeleteProfileData({ + onDeleteAssetProfile({ dataSource: element.dataSource, symbol: element.symbol }) diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts index 73bf47f7f..87562460a 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.module.ts @@ -4,6 +4,7 @@ import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter'; import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; +import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatMenuModule } from '@angular/material/menu'; import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSortModule } from '@angular/material/sort'; @@ -25,6 +26,7 @@ import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/ GfCreateAssetProfileDialogModule, GfSymbolModule, MatButtonModule, + MatCheckboxModule, MatMenuModule, MatPaginatorModule, MatSortModule, diff --git a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts index 44319a7a3..4ea6dd82e 100644 --- a/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts +++ b/apps/client/src/app/components/admin-market-data/admin-market-data.service.ts @@ -2,13 +2,13 @@ import { AdminService } from '@ghostfolio/client/services/admin.service'; import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { Injectable } from '@angular/core'; -import { takeUntil } from 'rxjs'; +import { EMPTY, catchError, finalize, forkJoin, takeUntil } from 'rxjs'; @Injectable() export class AdminMarketDataService { public constructor(private adminService: AdminService) {} - public deleteProfileData({ dataSource, symbol }: UniqueAsset) { + public deleteAssetProfile({ dataSource, symbol }: UniqueAsset) { const confirmation = confirm( $localize`Do you really want to delete this asset profile?` ); @@ -23,4 +23,31 @@ export class AdminMarketDataService { }); } } + + public deleteAssetProfiles(uniqueAssets: UniqueAsset[]) { + const confirmation = confirm( + $localize`Do you really want to delete these asset profiles?` + ); + + if (confirmation) { + const deleteRequests = uniqueAssets.map(({ dataSource, symbol }) => { + return this.adminService.deleteProfileData({ dataSource, symbol }); + }); + + forkJoin(deleteRequests) + .pipe( + catchError(() => { + alert($localize`Oops! Could not delete asset profiles.`); + + return EMPTY; + }), + finalize(() => { + setTimeout(() => { + window.location.reload(); + }, 300); + }) + ) + .subscribe(() => {}); + } + } } diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts index bf8de8ca9..0fbacb815 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/asset-profile-dialog.component.ts @@ -176,7 +176,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit { } public onDeleteProfileData({ dataSource, symbol }: UniqueAsset) { - this.adminMarketDataService.deleteProfileData({ dataSource, symbol }); + this.adminMarketDataService.deleteAssetProfile({ dataSource, symbol }); this.dialogRef.close(); }