Feature/bulk deletion for asset profiles (#3531)
* Add support for bulk deletion of asset profiles * Update changelog
This commit is contained in:
parent
83b5cfff1f
commit
554136cdcd
@ -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
|
||||
|
@ -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<Partial<SymbolProfile>>;
|
||||
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,
|
||||
|
@ -20,6 +20,27 @@
|
||||
matSortDirection="asc"
|
||||
[dataSource]="dataSource"
|
||||
>
|
||||
<ng-container matColumnDef="select">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell></th>
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
@if (
|
||||
!(
|
||||
element.activitiesCount !== 0 ||
|
||||
element.isBenchmark ||
|
||||
element.symbol.startsWith(ghostfolioScraperApiSymbolPrefix)
|
||||
)
|
||||
) {
|
||||
<mat-checkbox
|
||||
color="primary"
|
||||
[checked]="selection.isSelected(element)"
|
||||
(change)="$event ? selection.toggle(element) : null"
|
||||
(click)="$event.stopPropagation()"
|
||||
>
|
||||
</mat-checkbox>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="symbol">
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
|
||||
<ng-container i18n>Symbol</ng-container>
|
||||
@ -152,6 +173,13 @@
|
||||
<button mat-menu-item (click)="onGatherProfileData()">
|
||||
<ng-container i18n>Gather Profile Data</ng-container>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="!selection.hasValue()"
|
||||
(click)="onDeleteAssetProfiles()"
|
||||
>
|
||||
<ng-container i18n>Delete Asset Profiles</ng-container>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="px-1 text-center" mat-cell>
|
||||
@ -186,7 +214,7 @@
|
||||
element.symbol.startsWith(ghostfolioScraperApiSymbolPrefix)
|
||||
"
|
||||
(click)="
|
||||
onDeleteProfileData({
|
||||
onDeleteAssetProfile({
|
||||
dataSource: element.dataSource,
|
||||
symbol: element.symbol
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user