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>[],
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()
};
}
});
}
}

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 { xRayRules } from '@ghostfolio/common/interfaces/x-ray-rule.interface';
import type {
ColorScheme,
DateRange,
@ -102,4 +103,7 @@ export class UpdateUserSettingDto {
@IsIn(<ViewMode[]>['DEFAULT', 'ZEN'])
@IsOptional()
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 { 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 = {
...(<UserSettings>this.request.user.Settings.settings),
...data
};
const userSettings: UserSettings = merge(
{},
<UserSettings>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

12
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) {

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 {
return {
baseCurrency: aUserSettings.baseCurrency,
isActive: true,
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
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 {
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 {
return {
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 {
return {
baseCurrency: aUserSettings.baseCurrency,
isActive: true,
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
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 {
return {
baseCurrency: aUserSettings.baseCurrency,
isActive: true,
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
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 {
return {
baseCurrency: aUserSettings.baseCurrency,
isActive: true,
isActive: aUserSettings.xRayRules[this.getKey()].isActive,
thresholdMax: 0.01
};
}

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

@ -14,12 +14,18 @@
} @else {
<div
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) {
<ion-icon name="checkmark-circle-outline" />
} @else {
} @else if (rule?.value === false) {
<ion-icon name="warning-outline" />
} @else {
<ion-icon name="close-outline" />
}
</div>
}
@ -44,7 +50,34 @@
} @else {
<div class="flex-grow-1">
<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>

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 {
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<void>();
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();
});
});
}
}

9
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 {}

5
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;
}

3
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;
}

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