diff --git a/apps/api/src/app/portfolio/rules.service.ts b/apps/api/src/app/portfolio/rules.service.ts index 85f7ed55b..7c0e83919 100644 --- a/apps/api/src/app/portfolio/rules.service.ts +++ b/apps/api/src/app/portfolio/rules.service.ts @@ -12,21 +12,23 @@ export class RulesService { aRules: Rule[], aUserSettings: UserSettings ) { - return aRules - .filter((rule) => { - return rule.getSettings(aUserSettings)?.isActive; - }) - .map((rule) => { + return aRules.map((rule) => { + if (rule.getSettings(aUserSettings)?.isActive) { const { evaluation, value } = rule.evaluate( rule.getSettings(aUserSettings) ); - return { evaluation, value, key: rule.getKey(), name: rule.getName() }; - }); + } else { + return { + key: rule.getKey(), + name: rule.getName() + }; + } + }); } } diff --git a/apps/api/src/app/user/update-user-setting.dto.ts b/apps/api/src/app/user/update-user-setting.dto.ts index 78e6c27a9..2473ab583 100644 --- a/apps/api/src/app/user/update-user-setting.dto.ts +++ b/apps/api/src/app/user/update-user-setting.dto.ts @@ -1,4 +1,5 @@ import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; +import { xRayRules } from '@ghostfolio/common/interfaces/x-ray-rule.interface'; import type { ColorScheme, DateRange, @@ -102,4 +103,7 @@ export class UpdateUserSettingDto { @IsIn(['DEFAULT', 'ZEN']) @IsOptional() viewMode?: ViewMode; + + @IsOptional() + xRayRules?: xRayRules; } diff --git a/apps/api/src/app/user/user.controller.ts b/apps/api/src/app/user/user.controller.ts index 7cd2002bd..1258341c4 100644 --- a/apps/api/src/app/user/user.controller.ts +++ b/apps/api/src/app/user/user.controller.ts @@ -24,7 +24,7 @@ import { JwtService } from '@nestjs/jwt'; import { AuthGuard } from '@nestjs/passport'; import { User as UserModel } from '@prisma/client'; import { StatusCodes, getReasonPhrase } from 'http-status-codes'; -import { size } from 'lodash'; +import { size, merge } from 'lodash'; import { DeleteOwnUserDto } from './delete-own-user.dto'; import { UserItem } from './interfaces/user-item.interface'; @@ -144,17 +144,17 @@ export class UserController { ); } - const userSettings: UserSettings = { - ...(this.request.user.Settings.settings), - ...data - }; + const userSettings: UserSettings = merge( + {}, + this.request.user.Settings.settings, + data + ); for (const key in userSettings) { if (userSettings[key] === false || userSettings[key] === null) { delete userSettings[key]; } } - return this.userService.updateUserSetting({ userSettings, userId: this.request.user.id diff --git a/apps/api/src/app/user/user.service.ts b/apps/api/src/app/user/user.service.ts index 7aa1dbbe8..8c3f87f16 100644 --- a/apps/api/src/app/user/user.service.ts +++ b/apps/api/src/app/user/user.service.ts @@ -197,6 +197,18 @@ export class UserService { (user.Settings.settings as UserSettings).viewMode = 'DEFAULT'; } + // Set default values for x-ray rules + if (!(user.Settings.settings as UserSettings).xRayRules) { + (user.Settings.settings as UserSettings).xRayRules = { + AccountClusterRiskCurrentInvestment: { isActive: true }, + AccountClusterRiskSingleAccount: { isActive: true }, + CurrencyClusterRiskBaseCurrencyCurrentInvestment: { isActive: true }, + CurrencyClusterRiskCurrentInvestment: { isActive: true }, + EmergencyFundSetup: { isActive: true }, + FeeRatioInitialInvestment: { isActive: true } + }; + } + let currentPermissions = getPermissions(user.role); if (!(user.Settings.settings as UserSettings).isExperimentalFeatures) { diff --git a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts index 3b1433da0..e25bb2f08 100644 --- a/apps/api/src/models/rules/account-cluster-risk/current-investment.ts +++ b/apps/api/src/models/rules/account-cluster-risk/current-investment.ts @@ -79,7 +79,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule { public getSettings(aUserSettings: UserSettings): Settings { return { baseCurrency: aUserSettings.baseCurrency, - isActive: true, + isActive: aUserSettings.xRayRules[this.getKey()].isActive, thresholdMax: 0.5 }; } diff --git a/apps/api/src/models/rules/account-cluster-risk/single-account.ts b/apps/api/src/models/rules/account-cluster-risk/single-account.ts index a47895c13..1f61b9659 100644 --- a/apps/api/src/models/rules/account-cluster-risk/single-account.ts +++ b/apps/api/src/models/rules/account-cluster-risk/single-account.ts @@ -36,7 +36,7 @@ export class AccountClusterRiskSingleAccount extends Rule { public getSettings(aUserSettings: UserSettings): RuleSettings { return { - isActive: true + isActive: aUserSettings.xRayRules[this.getKey()].isActive }; } } diff --git a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts index 372250dbc..1258eb889 100644 --- a/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts +++ b/apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts @@ -65,7 +65,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule { public getSettings(aUserSettings: UserSettings): Settings { return { baseCurrency: aUserSettings.baseCurrency, - isActive: true, + isActive: aUserSettings.xRayRules[this.getKey()].isActive, thresholdMax: 0.5 }; } diff --git a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts index 0f56e6e38..0ba7a109c 100644 --- a/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts +++ b/apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts @@ -35,7 +35,7 @@ export class EmergencyFundSetup extends Rule { public getSettings(aUserSettings: UserSettings): Settings { return { baseCurrency: aUserSettings.baseCurrency, - isActive: true, + isActive: aUserSettings.xRayRules[this.getKey()].isActive, thresholdMin: 0 }; } diff --git a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts b/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts index ba487f819..09029fd3e 100644 --- a/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts +++ b/apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts @@ -46,7 +46,7 @@ export class FeeRatioInitialInvestment extends Rule { public getSettings(aUserSettings: UserSettings): Settings { return { baseCurrency: aUserSettings.baseCurrency, - isActive: true, + isActive: aUserSettings.xRayRules[this.getKey()].isActive, thresholdMax: 0.01 }; } diff --git a/apps/client/src/app/components/rule/rule.component.html b/apps/client/src/app/components/rule/rule.component.html index c2f76a321..6493c2c0b 100644 --- a/apps/client/src/app/components/rule/rule.component.html +++ b/apps/client/src/app/components/rule/rule.component.html @@ -14,12 +14,18 @@ } @else {
@if (rule?.value === true) { - } @else { + } @else if (rule?.value === false) { + } @else { + }
} @@ -44,7 +50,34 @@ } @else {
{{ rule?.name }}
-
{{ rule?.evaluation }}
+
+ @if (rule?.evaluation) { + {{ rule?.evaluation }} + } @else { + Rule is disabled + } +
+ + + + +
+
} diff --git a/apps/client/src/app/components/rule/rule.component.ts b/apps/client/src/app/components/rule/rule.component.ts index 61514939f..09a56bb46 100644 --- a/apps/client/src/app/components/rule/rule.component.ts +++ b/apps/client/src/app/components/rule/rule.component.ts @@ -1,11 +1,16 @@ +import { UpdateUserSettingDto } from '@ghostfolio/api/app/user/update-user-setting.dto'; +import { DataService } from '@ghostfolio/client/services/data.service'; import { PortfolioReportRule } from '@ghostfolio/common/interfaces'; import { + ChangeDetectorRef, ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'gf-rule', @@ -16,8 +21,39 @@ import { export class RuleComponent implements OnInit { @Input() isLoading: boolean; @Input() rule: PortfolioReportRule; + private unsubscribeSubject = new Subject(); - public constructor() {} + public constructor( + private changeDetectorRef: ChangeDetectorRef, + private dataService: DataService + ) {} public ngOnInit() {} + + public onUpdateAccount(rule: PortfolioReportRule) { + let settings: UpdateUserSettingDto = { + xRayRules: { + [rule.key]: { isActive: !('evaluation' in rule) } + } + }; + this.dataService + .putUserSetting(settings) + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe(() => { + this.dataService + .fetchPortfolioReport() + .pipe(takeUntil(this.unsubscribeSubject)) + .subscribe((report) => { + for (const ruleGroup in report.rules) { + for (const singleRule in report.rules[ruleGroup]) { + if (report.rules[ruleGroup][singleRule]['key'] === rule.key) { + this.rule = report.rules[ruleGroup][singleRule]; + break; + } + } + } + this.changeDetectorRef.markForCheck(); + }); + }); + } } diff --git a/apps/client/src/app/components/rule/rule.module.ts b/apps/client/src/app/components/rule/rule.module.ts index 40e49cadd..d2cba5b25 100644 --- a/apps/client/src/app/components/rule/rule.module.ts +++ b/apps/client/src/app/components/rule/rule.module.ts @@ -1,5 +1,7 @@ import { CommonModule } from '@angular/common'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; +import { MatButtonModule } from '@angular/material/button'; +import { MatMenuModule } from '@angular/material/menu'; import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader'; import { RuleComponent } from './rule.component'; @@ -7,7 +9,12 @@ import { RuleComponent } from './rule.component'; @NgModule({ declarations: [RuleComponent], exports: [RuleComponent], - imports: [CommonModule, NgxSkeletonLoaderModule], + imports: [ + CommonModule, + MatButtonModule, + MatMenuModule, + NgxSkeletonLoaderModule + ], schemas: [CUSTOM_ELEMENTS_SCHEMA] }) export class GfRuleModule {} diff --git a/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts b/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts index 5ba8ccef3..7e0be4fb1 100644 --- a/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts +++ b/libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts @@ -1,5 +1,6 @@ export interface PortfolioReportRule { - evaluation: string; + evaluation?: string; + key: string; name: string; - value: boolean; + value?: boolean; } diff --git a/libs/common/src/lib/interfaces/user-settings.interface.ts b/libs/common/src/lib/interfaces/user-settings.interface.ts index 5c88e3f4b..eaeae5206 100644 --- a/libs/common/src/lib/interfaces/user-settings.interface.ts +++ b/libs/common/src/lib/interfaces/user-settings.interface.ts @@ -5,6 +5,8 @@ import { ViewMode } from '@ghostfolio/common/types'; +import { xRayRules } from './x-ray-rule.interface'; + export interface UserSettings { annualInterestRate?: number; baseCurrency?: string; @@ -23,4 +25,5 @@ export interface UserSettings { retirementDate?: string; savingsRate?: number; viewMode?: ViewMode; + xRayRules?: xRayRules; } diff --git a/libs/common/src/lib/interfaces/x-ray-rule.interface.ts b/libs/common/src/lib/interfaces/x-ray-rule.interface.ts new file mode 100644 index 000000000..d0881f2c8 --- /dev/null +++ b/libs/common/src/lib/interfaces/x-ray-rule.interface.ts @@ -0,0 +1,12 @@ +export interface xRayRules { + AccountClusterRiskCurrentInvestment?: Rule; + AccountClusterRiskSingleAccount?: Rule; + CurrencyClusterRiskBaseCurrencyCurrentInvestment?: Rule; + CurrencyClusterRiskCurrentInvestment?: Rule; + EmergencyFundSetup?: Rule; + FeeRatioInitialInvestment?: Rule; +} + +interface Rule { + isActive: boolean; +}