Browse Source

Add option to disable x-ray rules

pull/3537/head
Sonlis 1 year ago
committed by Thomas Kaul
parent
commit
68cd65c8ed
  1. 14
      apps/api/src/app/portfolio/rules.service.ts
  2. 4
      apps/api/src/app/user/update-user-setting.dto.ts
  3. 12
      apps/api/src/app/user/user.controller.ts
  4. 12
      apps/api/src/app/user/user.service.ts
  5. 2
      apps/api/src/models/rules/account-cluster-risk/current-investment.ts
  6. 2
      apps/api/src/models/rules/account-cluster-risk/single-account.ts
  7. 2
      apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts
  8. 2
      apps/api/src/models/rules/currency-cluster-risk/current-investment.ts
  9. 2
      apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts
  10. 2
      apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts
  11. 39
      apps/client/src/app/components/rule/rule.component.html
  12. 38
      apps/client/src/app/components/rule/rule.component.ts
  13. 9
      apps/client/src/app/components/rule/rule.module.ts
  14. 5
      libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts
  15. 3
      libs/common/src/lib/interfaces/user-settings.interface.ts
  16. 12
      libs/common/src/lib/interfaces/x-ray-rule.interface.ts

14
apps/api/src/app/portfolio/rules.service.ts

@ -12,21 +12,23 @@ export class RulesService {
aRules: Rule<T>[], aRules: Rule<T>[],
aUserSettings: UserSettings aUserSettings: UserSettings
) { ) {
return aRules return aRules.map((rule) => {
.filter((rule) => { if (rule.getSettings(aUserSettings)?.isActive) {
return rule.getSettings(aUserSettings)?.isActive;
})
.map((rule) => {
const { evaluation, value } = rule.evaluate( const { evaluation, value } = rule.evaluate(
rule.getSettings(aUserSettings) rule.getSettings(aUserSettings)
); );
return { return {
evaluation, evaluation,
value, value,
key: rule.getKey(), key: rule.getKey(),
name: rule.getName() name: rule.getName()
}; };
} else {
return {
key: rule.getKey(),
name: rule.getName()
};
}
}); });
} }
} }

4
apps/api/src/app/user/update-user-setting.dto.ts

@ -1,4 +1,5 @@
import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code'; import { IsCurrencyCode } from '@ghostfolio/api/validators/is-currency-code';
import { xRayRules } from '@ghostfolio/common/interfaces/x-ray-rule.interface';
import type { import type {
ColorScheme, ColorScheme,
DateRange, DateRange,
@ -102,4 +103,7 @@ export class UpdateUserSettingDto {
@IsIn(<ViewMode[]>['DEFAULT', 'ZEN']) @IsIn(<ViewMode[]>['DEFAULT', 'ZEN'])
@IsOptional() @IsOptional()
viewMode?: ViewMode; viewMode?: ViewMode;
@IsOptional()
xRayRules?: xRayRules;
} }

12
apps/api/src/app/user/user.controller.ts

@ -24,7 +24,7 @@ import { JwtService } from '@nestjs/jwt';
import { AuthGuard } from '@nestjs/passport'; import { AuthGuard } from '@nestjs/passport';
import { User as UserModel } from '@prisma/client'; import { User as UserModel } from '@prisma/client';
import { StatusCodes, getReasonPhrase } from 'http-status-codes'; import { StatusCodes, getReasonPhrase } from 'http-status-codes';
import { size } from 'lodash'; import { size, merge } from 'lodash';
import { DeleteOwnUserDto } from './delete-own-user.dto'; import { DeleteOwnUserDto } from './delete-own-user.dto';
import { UserItem } from './interfaces/user-item.interface'; import { UserItem } from './interfaces/user-item.interface';
@ -144,17 +144,17 @@ export class UserController {
); );
} }
const userSettings: UserSettings = { const userSettings: UserSettings = merge(
...(<UserSettings>this.request.user.Settings.settings), {},
...data <UserSettings>this.request.user.Settings.settings,
}; data
);
for (const key in userSettings) { for (const key in userSettings) {
if (userSettings[key] === false || userSettings[key] === null) { if (userSettings[key] === false || userSettings[key] === null) {
delete userSettings[key]; delete userSettings[key];
} }
} }
return this.userService.updateUserSetting({ return this.userService.updateUserSetting({
userSettings, userSettings,
userId: this.request.user.id userId: this.request.user.id

12
apps/api/src/app/user/user.service.ts

@ -197,6 +197,18 @@ export class UserService {
(user.Settings.settings as UserSettings).viewMode = 'DEFAULT'; (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); let currentPermissions = getPermissions(user.role);
if (!(user.Settings.settings as UserSettings).isExperimentalFeatures) { if (!(user.Settings.settings as UserSettings).isExperimentalFeatures) {

2
apps/api/src/models/rules/account-cluster-risk/current-investment.ts

@ -79,7 +79,7 @@ export class AccountClusterRiskCurrentInvestment extends Rule<Settings> {
public getSettings(aUserSettings: UserSettings): Settings { public getSettings(aUserSettings: UserSettings): Settings {
return { return {
baseCurrency: aUserSettings.baseCurrency, baseCurrency: aUserSettings.baseCurrency,
isActive: true, isActive: aUserSettings.xRayRules[this.getKey()].isActive,
thresholdMax: 0.5 thresholdMax: 0.5
}; };
} }

2
apps/api/src/models/rules/account-cluster-risk/single-account.ts

@ -36,7 +36,7 @@ export class AccountClusterRiskSingleAccount extends Rule<RuleSettings> {
public getSettings(aUserSettings: UserSettings): RuleSettings { public getSettings(aUserSettings: UserSettings): RuleSettings {
return { return {
isActive: true isActive: aUserSettings.xRayRules[this.getKey()].isActive
}; };
} }
} }

2
apps/api/src/models/rules/currency-cluster-risk/base-currency-current-investment.ts

@ -65,7 +65,7 @@ export class CurrencyClusterRiskBaseCurrencyCurrentInvestment extends Rule<Setti
public getSettings(aUserSettings: UserSettings): Settings { public getSettings(aUserSettings: UserSettings): Settings {
return { return {
baseCurrency: aUserSettings.baseCurrency, baseCurrency: aUserSettings.baseCurrency,
isActive: true isActive: aUserSettings.xRayRules[this.getKey()].isActive
}; };
} }
} }

2
apps/api/src/models/rules/currency-cluster-risk/current-investment.ts

@ -65,7 +65,7 @@ export class CurrencyClusterRiskCurrentInvestment extends Rule<Settings> {
public getSettings(aUserSettings: UserSettings): Settings { public getSettings(aUserSettings: UserSettings): Settings {
return { return {
baseCurrency: aUserSettings.baseCurrency, baseCurrency: aUserSettings.baseCurrency,
isActive: true, isActive: aUserSettings.xRayRules[this.getKey()].isActive,
thresholdMax: 0.5 thresholdMax: 0.5
}; };
} }

2
apps/api/src/models/rules/emergency-fund/emergency-fund-setup.ts

@ -35,7 +35,7 @@ export class EmergencyFundSetup extends Rule<Settings> {
public getSettings(aUserSettings: UserSettings): Settings { public getSettings(aUserSettings: UserSettings): Settings {
return { return {
baseCurrency: aUserSettings.baseCurrency, baseCurrency: aUserSettings.baseCurrency,
isActive: true, isActive: aUserSettings.xRayRules[this.getKey()].isActive,
thresholdMin: 0 thresholdMin: 0
}; };
} }

2
apps/api/src/models/rules/fees/fee-ratio-initial-investment.ts

@ -46,7 +46,7 @@ export class FeeRatioInitialInvestment extends Rule<Settings> {
public getSettings(aUserSettings: UserSettings): Settings { public getSettings(aUserSettings: UserSettings): Settings {
return { return {
baseCurrency: aUserSettings.baseCurrency, baseCurrency: aUserSettings.baseCurrency,
isActive: true, isActive: aUserSettings.xRayRules[this.getKey()].isActive,
thresholdMax: 0.01 thresholdMax: 0.01
}; };
} }

39
apps/client/src/app/components/rule/rule.component.html

@ -14,12 +14,18 @@
} @else { } @else {
<div <div
class="align-items-center d-flex icon-container mr-2 px-2" class="align-items-center d-flex icon-container mr-2 px-2"
[ngClass]="{ okay: rule?.value === true, warn: rule?.value === false }" [ngClass]="{
okay: rule?.value === true,
warn: rule?.value === false,
disabled: rule?.value === undefined
}"
> >
@if (rule?.value === true) { @if (rule?.value === true) {
<ion-icon name="checkmark-circle-outline" /> <ion-icon name="checkmark-circle-outline" />
} @else { } @else if (rule?.value === false) {
<ion-icon name="warning-outline" /> <ion-icon name="warning-outline" />
} @else {
<ion-icon name="close-outline" />
} }
</div> </div>
} }
@ -44,7 +50,34 @@
} @else { } @else {
<div class="flex-grow-1"> <div class="flex-grow-1">
<div class="h6 my-1">{{ rule?.name }}</div> <div class="h6 my-1">{{ rule?.name }}</div>
<div class="evaluation">{{ rule?.evaluation }}</div> <div class="evaluation">
@if (rule?.evaluation) {
{{ rule?.evaluation }}
} @else {
Rule is disabled
}
<div class="float-right">
<button
class="mx-1 no-min-width px-2"
mat-button
[matMenuTriggerFor]="accountMenu"
(click)="$event.stopPropagation()"
>
<ion-icon name="ellipsis-horizontal" />
</button>
<mat-menu #accountMenu="matMenu" xPosition="before">
<button mat-menu-item (click)="onUpdateAccount(rule)">
<span class="align-items-center d-flex">
@if (rule?.evaluation) {
Disable
} @else {
Enable
}
</span>
</button>
</mat-menu>
</div>
</div>
</div> </div>
} }
</div> </div>

38
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 { PortfolioReportRule } from '@ghostfolio/common/interfaces';
import { import {
ChangeDetectorRef,
ChangeDetectionStrategy, ChangeDetectionStrategy,
Component, Component,
Input, Input,
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'gf-rule', selector: 'gf-rule',
@ -16,8 +21,39 @@ import {
export class RuleComponent implements OnInit { export class RuleComponent implements OnInit {
@Input() isLoading: boolean; @Input() isLoading: boolean;
@Input() rule: PortfolioReportRule; @Input() rule: PortfolioReportRule;
private unsubscribeSubject = new Subject<void>();
public constructor() {} public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService
) {}
public ngOnInit() {} 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();
});
});
}
} }

9
apps/client/src/app/components/rule/rule.module.ts

@ -1,5 +1,7 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; 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 { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { RuleComponent } from './rule.component'; import { RuleComponent } from './rule.component';
@ -7,7 +9,12 @@ import { RuleComponent } from './rule.component';
@NgModule({ @NgModule({
declarations: [RuleComponent], declarations: [RuleComponent],
exports: [RuleComponent], exports: [RuleComponent],
imports: [CommonModule, NgxSkeletonLoaderModule], imports: [
CommonModule,
MatButtonModule,
MatMenuModule,
NgxSkeletonLoaderModule
],
schemas: [CUSTOM_ELEMENTS_SCHEMA] schemas: [CUSTOM_ELEMENTS_SCHEMA]
}) })
export class GfRuleModule {} export class GfRuleModule {}

5
libs/common/src/lib/interfaces/portfolio-report-rule.interface.ts

@ -1,5 +1,6 @@
export interface PortfolioReportRule { export interface PortfolioReportRule {
evaluation: string; evaluation?: string;
key: string;
name: string; name: string;
value: boolean; value?: boolean;
} }

3
libs/common/src/lib/interfaces/user-settings.interface.ts

@ -5,6 +5,8 @@ import {
ViewMode ViewMode
} from '@ghostfolio/common/types'; } from '@ghostfolio/common/types';
import { xRayRules } from './x-ray-rule.interface';
export interface UserSettings { export interface UserSettings {
annualInterestRate?: number; annualInterestRate?: number;
baseCurrency?: string; baseCurrency?: string;
@ -23,4 +25,5 @@ export interface UserSettings {
retirementDate?: string; retirementDate?: string;
savingsRate?: number; savingsRate?: number;
viewMode?: ViewMode; viewMode?: ViewMode;
xRayRules?: xRayRules;
} }

12
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;
}
Loading…
Cancel
Save