Browse Source

Feature/support delete activities with filtering (#3394)

* Support delete activities with filtering

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/3355/head
Gerard Du Pre 5 months ago
committed by GitHub
parent
commit
8319b216bb
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 16
      apps/api/src/app/order/order.controller.ts
  3. 30
      apps/api/src/app/order/order.service.ts
  4. 37
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  5. 2
      apps/client/src/app/pages/portfolio/activities/activities-page.html
  6. 10
      apps/client/src/app/services/data.service.ts
  7. 4
      libs/ui/src/lib/activities-table/activities-table.component.html
  8. 16
      libs/ui/src/lib/activities-table/activities-table.component.ts

2
CHANGELOG.md

@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Disabled the button to delete all activities on the portfolio activities page if there are active filters - Improved the delete all activities functionality on the portfolio activities page to work with the filters of the assistant
- Upgraded `Nx` from version `18.3.3` to `19.0.2` - Upgraded `Nx` from version `18.3.3` to `19.0.2`
### Fixed ### Fixed

16
apps/api/src/app/order/order.controller.ts

@ -11,7 +11,7 @@ import {
DATA_GATHERING_QUEUE_PRIORITY_HIGH, DATA_GATHERING_QUEUE_PRIORITY_HIGH,
HEADER_KEY_IMPERSONATION HEADER_KEY_IMPERSONATION
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { permissions } from '@ghostfolio/common/permissions';
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types'; import type { DateRange, RequestWithUser } from '@ghostfolio/common/types';
import { import {
@ -53,8 +53,20 @@ export class OrderController {
@Delete() @Delete()
@HasPermission(permissions.deleteOrder) @HasPermission(permissions.deleteOrder)
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
public async deleteOrders(): Promise<number> { public async deleteOrders(
@Query('accounts') filterByAccounts?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('tags') filterByTags?: string
): Promise<number> {
const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts,
filterByAssetClasses,
filterByTags
});
return this.orderService.deleteOrders({ return this.orderService.deleteOrders({
filters,
userCurrency: this.request.user.Settings.settings.baseCurrency,
userId: this.request.user.id userId: this.request.user.id
}); });
} }

30
apps/api/src/app/order/order.service.ts

@ -194,16 +194,36 @@ export class OrderService {
return order; return order;
} }
public async deleteOrders(where: Prisma.OrderWhereInput): Promise<number> { public async deleteOrders({
filters,
userCurrency,
userId
}: {
filters?: Filter[];
userCurrency: string;
userId: string;
}): Promise<number> {
const { activities } = await this.getOrders({
filters,
userId,
userCurrency,
includeDrafts: true,
withExcludedAccounts: true
});
const { count } = await this.prismaService.order.deleteMany({ const { count } = await this.prismaService.order.deleteMany({
where where: {
id: {
in: activities.map(({ id }) => {
return id;
})
}
}
}); });
this.eventEmitter.emit( this.eventEmitter.emit(
PortfolioChangedEvent.getName(), PortfolioChangedEvent.getName(),
new PortfolioChangedEvent({ new PortfolioChangedEvent({ userId })
userId: <string>where.userId
})
); );
return count; return count;

37
apps/client/src/app/pages/portfolio/activities/activities-page.component.ts

@ -142,32 +142,24 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
this.openCreateActivityDialog(aActivity); this.openCreateActivityDialog(aActivity);
} }
public onDeleteActivity(aId: string) { public onDeleteActivities() {
this.dataService this.dataService
.deleteActivity(aId) .deleteActivities({
filters: this.userService.getFilters()
})
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe({ .subscribe(() => {
next: () => { this.fetchActivities();
this.fetchActivities();
}
}); });
} }
public onDeleteAllActivities() { public onDeleteActivity(aId: string) {
const confirmation = confirm( this.dataService
$localize`Do you really want to delete all your activities?` .deleteActivity(aId)
); .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => {
if (confirmation) { this.fetchActivities();
this.dataService });
.deleteAllActivities()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe({
next: () => {
this.fetchActivities();
}
});
}
} }
public onExport(activityIds?: string[]) { public onExport(activityIds?: string[]) {
@ -348,7 +340,6 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
hasPermission(this.user.permissions, permissions.createOrder); hasPermission(this.user.permissions, permissions.createOrder);
this.hasPermissionToDeleteActivity = this.hasPermissionToDeleteActivity =
!this.hasImpersonationId && !this.hasImpersonationId &&
hasPermission(this.user.permissions, permissions.deleteOrder) && hasPermission(this.user.permissions, permissions.deleteOrder);
!this.userService.hasFilters();
} }
} }

2
apps/client/src/app/pages/portfolio/activities/activities-page.html

@ -20,10 +20,10 @@
[sortColumn]="sortColumn" [sortColumn]="sortColumn"
[sortDirection]="sortDirection" [sortDirection]="sortDirection"
[totalItems]="totalItems" [totalItems]="totalItems"
(activitiesDeleted)="onDeleteActivities()"
(activityDeleted)="onDeleteActivity($event)" (activityDeleted)="onDeleteActivity($event)"
(activityToClone)="onCloneActivity($event)" (activityToClone)="onCloneActivity($event)"
(activityToUpdate)="onUpdateActivity($event)" (activityToUpdate)="onUpdateActivity($event)"
(deleteAllActivities)="onDeleteAllActivities()"
(export)="onExport()" (export)="onExport()"
(exportDrafts)="onExportDrafts($event)" (exportDrafts)="onExportDrafts($event)"
(import)="onImport()" (import)="onImport()"

10
apps/client/src/app/services/data.service.ts

@ -256,12 +256,14 @@ export class DataService {
return this.http.delete<any>(`/api/v1/account-balance/${aId}`); return this.http.delete<any>(`/api/v1/account-balance/${aId}`);
} }
public deleteActivity(aId: string) { public deleteActivities({ filters }) {
return this.http.delete<any>(`/api/v1/order/${aId}`); let params = this.buildFiltersAsQueryParams({ filters });
return this.http.delete<any>(`/api/v1/order`, { params });
} }
public deleteAllActivities() { public deleteActivity(aId: string) {
return this.http.delete<any>(`/api/v1/order`); return this.http.delete<any>(`/api/v1/order/${aId}`);
} }
public deleteBenchmark({ dataSource, symbol }: UniqueAsset) { public deleteBenchmark({ dataSource, symbol }: UniqueAsset) {

4
libs/ui/src/lib/activities-table/activities-table.component.html

@ -59,11 +59,11 @@
class="align-items-center d-flex" class="align-items-center d-flex"
mat-menu-item mat-menu-item
[disabled]="!hasPermissionToDeleteActivity" [disabled]="!hasPermissionToDeleteActivity"
(click)="onDeleteAllActivities()" (click)="onDeleteActivities()"
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="trash-outline" /> <ion-icon class="mr-2" name="trash-outline" />
<span i18n>Delete all Activities</span> <span i18n>Delete Activities</span>
</span> </span>
</button> </button>
</mat-menu> </mat-menu>

16
libs/ui/src/lib/activities-table/activities-table.component.ts

@ -92,10 +92,10 @@ export class GfActivitiesTableComponent
@Input() sortDisabled = false; @Input() sortDisabled = false;
@Input() totalItems = Number.MAX_SAFE_INTEGER; @Input() totalItems = Number.MAX_SAFE_INTEGER;
@Output() activitiesDeleted = new EventEmitter<void>();
@Output() activityDeleted = new EventEmitter<string>(); @Output() activityDeleted = new EventEmitter<string>();
@Output() activityToClone = new EventEmitter<OrderWithAccount>(); @Output() activityToClone = new EventEmitter<OrderWithAccount>();
@Output() activityToUpdate = new EventEmitter<OrderWithAccount>(); @Output() activityToUpdate = new EventEmitter<OrderWithAccount>();
@Output() deleteAllActivities = new EventEmitter<void>();
@Output() export = new EventEmitter<void>(); @Output() export = new EventEmitter<void>();
@Output() exportDrafts = new EventEmitter<string[]>(); @Output() exportDrafts = new EventEmitter<string[]>();
@Output() import = new EventEmitter<void>(); @Output() import = new EventEmitter<void>();
@ -211,6 +211,16 @@ export class GfActivitiesTableComponent
this.activityToClone.emit(aActivity); this.activityToClone.emit(aActivity);
} }
public onDeleteActivities() {
const confirmation = confirm(
$localize`Do you really want to delete these activities?`
);
if (confirmation) {
this.activitiesDeleted.emit();
}
}
public onDeleteActivity(aId: string) { public onDeleteActivity(aId: string) {
const confirmation = confirm( const confirmation = confirm(
$localize`Do you really want to delete this activity?` $localize`Do you really want to delete this activity?`
@ -241,10 +251,6 @@ export class GfActivitiesTableComponent
); );
} }
public onDeleteAllActivities() {
this.deleteAllActivities.emit();
}
public onImport() { public onImport() {
this.import.emit(); this.import.emit();
} }

Loading…
Cancel
Save