Browse Source

Respect activity type and date range filter

pull/7492/head
Thomas Kaul 1 month ago
parent
commit
1fdd60250d
  1. 2
      apps/api/src/app/activities/activities-filter.dto.ts
  2. 4
      apps/api/src/app/activities/activities.controller.ts
  3. 4
      apps/api/src/app/activities/get-activities.dto.ts
  4. 38
      apps/api/src/app/export/export.controller.ts
  5. 14
      apps/api/src/app/export/get-export.dto.ts
  6. 9
      libs/ui/src/lib/activities-table/activities-table.component.html
  7. 7
      libs/ui/src/lib/activities-table/activities-table.component.ts

2
apps/api/src/app/activities/delete-activities.dto.ts → apps/api/src/app/activities/activities-filter.dto.ts

@ -9,7 +9,7 @@ import { isString } from 'lodash';
// A named date range or a calendar year like '2024', '2023', '2022', etc. // A named date range or a calendar year like '2024', '2023', '2022', etc.
const DATE_RANGE_PATTERN = new RegExp(`^(${DATE_RANGES.join('|')}|\\d{4})$`); const DATE_RANGE_PATTERN = new RegExp(`^(${DATE_RANGES.join('|')}|\\d{4})$`);
export class DeleteActivitiesDto { export class ActivitiesFilterDto {
@IsOptional() @IsOptional()
@IsString() @IsString()
accounts?: string; accounts?: string;

4
apps/api/src/app/activities/activities.controller.ts

@ -41,8 +41,8 @@ import { Order } from '@prisma/client';
import { parseISO } from 'date-fns'; import { parseISO } from 'date-fns';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { ActivitiesFilterDto } from './activities-filter.dto';
import { ActivitiesService } from './activities.service'; import { ActivitiesService } from './activities.service';
import { DeleteActivitiesDto } from './delete-activities.dto';
import { GetActivitiesDto } from './get-activities.dto'; import { GetActivitiesDto } from './get-activities.dto';
@Controller('activities') @Controller('activities')
@ -62,7 +62,7 @@ export class ActivitiesController {
@UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor)
public async deleteActivities( public async deleteActivities(
@Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string, @Headers(HEADER_KEY_IMPERSONATION.toLowerCase()) impersonationId: string,
@Query() query: DeleteActivitiesDto @Query() query: ActivitiesFilterDto
): Promise<number> { ): Promise<number> {
if (impersonationId) { if (impersonationId) {
throw new HttpException( throw new HttpException(

4
apps/api/src/app/activities/get-activities.dto.ts

@ -2,9 +2,9 @@ import { Prisma } from '@prisma/client';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsIn, IsInt, IsOptional, Min } from 'class-validator'; import { IsIn, IsInt, IsOptional, Min } from 'class-validator';
import { DeleteActivitiesDto } from './delete-activities.dto'; import { ActivitiesFilterDto } from './activities-filter.dto';
export class GetActivitiesDto extends DeleteActivitiesDto { export class GetActivitiesDto extends ActivitiesFilterDto {
@IsInt() @IsInt()
@IsOptional() @IsOptional()
@Min(0) @Min(0)

38
apps/api/src/app/export/export.controller.ts

@ -4,7 +4,7 @@ import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interc
import { ApiService } from '@ghostfolio/api/services/api/api.service'; import { ApiService } from '@ghostfolio/api/services/api/api.service';
import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper'; import { getIntervalFromDateRange } from '@ghostfolio/common/calculation-helper';
import { ExportResponse } from '@ghostfolio/common/interfaces'; import { ExportResponse } from '@ghostfolio/common/interfaces';
import type { DateRange, RequestWithUser } from '@ghostfolio/common/types'; import type { RequestWithUser } from '@ghostfolio/common/types';
import { import {
Controller, Controller,
@ -16,9 +16,9 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { REQUEST } from '@nestjs/core'; import { REQUEST } from '@nestjs/core';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { Type as ActivityType } from '@prisma/client';
import { ExportService } from './export.service'; import { ExportService } from './export.service';
import { GetExportDto } from './get-export.dto';
@Controller('export') @Controller('export')
export class ExportController { export class ExportController {
@ -32,40 +32,30 @@ export class ExportController {
@UseGuards(AuthGuard('jwt'), HasPermissionGuard) @UseGuards(AuthGuard('jwt'), HasPermissionGuard)
@UseInterceptors(TransformDataSourceInRequestInterceptor) @UseInterceptors(TransformDataSourceInRequestInterceptor)
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async export( public async export(@Query() query: GetExportDto): Promise<ExportResponse> {
@Query('accounts') filterByAccounts?: string,
@Query('activityIds') filterByActivityIds?: string,
@Query('activityTypes') filterByTypes?: string,
@Query('assetClasses') filterByAssetClasses?: string,
@Query('dataSource') filterByDataSource?: string,
@Query('range') dateRange?: DateRange,
@Query('symbol') filterBySymbol?: string,
@Query('tags') filterByTags?: string
): Promise<ExportResponse> {
const activityIds = filterByActivityIds?.split(',') ?? [];
const activityTypes = (filterByTypes?.split(',') as ActivityType[]) ?? [];
let endDate: Date; let endDate: Date;
let startDate: Date; let startDate: Date;
if (dateRange) { if (query.range) {
({ endDate, startDate } = getIntervalFromDateRange({ dateRange })); ({ endDate, startDate } = getIntervalFromDateRange({
dateRange: query.range
}));
} }
const filters = this.apiService.buildFiltersFromQueryParams({ const filters = this.apiService.buildFiltersFromQueryParams({
filterByAccounts, filterByAccounts: query.accounts,
filterByAssetClasses, filterByAssetClasses: query.assetClasses,
filterByDataSource, filterByDataSource: query.dataSource,
filterBySymbol, filterBySymbol: query.symbol,
filterByTags filterByTags: query.tags
}); });
return this.exportService.export({ return this.exportService.export({
activityIds,
activityTypes,
endDate, endDate,
filters, filters,
startDate, startDate,
activityIds: query.activityIds,
activityTypes: query.activityTypes,
userId: this.request.user.id, userId: this.request.user.id,
userSettings: this.request.user.settings.settings userSettings: this.request.user.settings.settings
}); });

14
apps/api/src/app/export/get-export.dto.ts

@ -0,0 +1,14 @@
import { ActivitiesFilterDto } from '@ghostfolio/api/app/activities/activities-filter.dto';
import { Transform, TransformFnParams } from 'class-transformer';
import { IsOptional, IsUUID } from 'class-validator';
import { isString } from 'lodash';
export class GetExportDto extends ActivitiesFilterDto {
@IsOptional()
@IsUUID(undefined, { each: true })
@Transform(({ value }: TransformFnParams) => {
return isString(value) ? value.split(',') : value;
})
activityIds?: string[];
}

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

@ -58,12 +58,19 @@
<button <button
class="align-items-center d-flex" class="align-items-center d-flex"
mat-menu-item mat-menu-item
[disabled]="dataSource()?.data.length === 0" [disabled]="!canExportActivities()"
(click)="onExport()" (click)="onExport()"
> >
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<ion-icon class="mr-2" name="cloud-download-outline" /> <ion-icon class="mr-2" name="cloud-download-outline" />
@if (canExportActivities()) {
<span i18n
>Export {{ totalItems > 1 ? totalItems : '' }}
{totalItems, plural, =1 {Activity} other {Activities}}</span
>
} @else {
<span i18n>Export Activities</span> <span i18n>Export Activities</span>
}
</span> </span>
</button> </button>
} }

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

@ -289,6 +289,13 @@ export class GfActivitiesTableComponent implements AfterViewInit, OnInit {
); );
} }
public canExportActivities() {
return (
(this.dataSource()?.data.length ?? 0) > 0 &&
this.hasPermissionToExportActivities
);
}
public isExcludedFromAnalysis(activity: Activity) { public isExcludedFromAnalysis(activity: Activity) {
return ( return (
(activity.account && isAccountExcluded(activity.account)) ?? (activity.account && isAccountExcluded(activity.account)) ??

Loading…
Cancel
Save