Feature/extend asset profile details dialog (#1469)

* Extend asset profile details dialog

* Update changelog
pull/1466/head^2
Thomas Kaul 2 years ago committed by GitHub
parent 4b74be50da
commit 3611684f17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Extended the asset profile details dialog in the admin control panel
## 1.214.0 - 19.11.2022
### Added

@ -147,7 +147,7 @@ export class AdminService {
countriesCount,
marketDataItemCount,
sectorsCount,
activityCount: symbolProfile._count.Order,
activitiesCount: symbolProfile._count.Order,
assetClass: symbolProfile.assetClass,
assetSubClass: symbolProfile.assetSubClass,
dataSource: symbolProfile.dataSource,
@ -165,8 +165,14 @@ export class AdminService {
dataSource,
symbol
}: UniqueAsset): Promise<AdminMarketDataDetails> {
return {
marketData: await this.marketDataService.marketDataItems({
const [[assetProfile], marketData] = await Promise.all([
this.symbolProfileService.getSymbolProfiles([
{
dataSource,
symbol
}
]),
this.marketDataService.marketDataItems({
orderBy: {
date: 'asc'
},
@ -175,6 +181,11 @@ export class AdminService {
symbol
}
})
]);
return {
assetProfile,
marketData
};
}

@ -43,7 +43,12 @@ export class SymbolProfileService {
): Promise<EnhancedSymbolProfile[]> {
return this.prismaService.symbolProfile
.findMany({
include: { SymbolProfileOverrides: true },
include: {
_count: {
select: { Order: true }
},
SymbolProfileOverrides: true
},
where: {
AND: [
{
@ -69,7 +74,12 @@ export class SymbolProfileService {
): Promise<EnhancedSymbolProfile[]> {
return this.prismaService.symbolProfile
.findMany({
include: { SymbolProfileOverrides: true },
include: {
_count: {
select: { Order: true }
},
SymbolProfileOverrides: true
},
where: {
id: {
in: symbolProfileIds.map((symbolProfileId) => {
@ -89,7 +99,12 @@ export class SymbolProfileService {
): Promise<EnhancedSymbolProfile[]> {
return this.prismaService.symbolProfile
.findMany({
include: { SymbolProfileOverrides: true },
include: {
_count: {
select: { Order: true }
},
SymbolProfileOverrides: true
},
where: {
symbol: {
in: symbols
@ -101,12 +116,14 @@ export class SymbolProfileService {
private getSymbols(
symbolProfiles: (SymbolProfile & {
_count: { Order: number };
SymbolProfileOverrides: SymbolProfileOverrides;
})[]
): EnhancedSymbolProfile[] {
return symbolProfiles.map((symbolProfile) => {
const item = {
...symbolProfile,
activitiesCount: 0,
countries: this.getCountries(
symbolProfile?.countries as unknown as Prisma.JsonArray
),
@ -115,6 +132,9 @@ export class SymbolProfileService {
symbolMapping: this.getSymbolMapping(symbolProfile)
};
item.activitiesCount = symbolProfile._count.Order;
delete item._count;
if (item.SymbolProfileOverrides) {
item.assetClass =
item.SymbolProfileOverrides.assetClass ?? item.assetClass;

@ -13,6 +13,7 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatTooltipModule } from '@angular/material/tooltip';
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { ServiceWorkerModule } from '@angular/service-worker';
import { MaterialCssVarsModule } from 'angular-material-css-vars';
import { MarkdownModule } from 'ngx-markdown';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -27,7 +28,6 @@ import { GfHeaderModule } from './components/header/header.module';
import { authInterceptorProviders } from './core/auth.interceptor';
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
import { LanguageService } from './core/language.service';
import { ServiceWorkerModule } from '@angular/service-worker';
export function NgxStripeFactory(): string {
return environment.stripePublicKey;

@ -63,10 +63,10 @@ export class AdminMarketDataComponent implements OnDestroy, OnInit {
'assetClass',
'assetSubClass',
'date',
'activityCount',
'activitiesCount',
'marketDataItemCount',
'countriesCount',
'sectorsCount',
'countriesCount',
'actions'
];
public filters$ = new Subject<Filter[]>();

@ -64,12 +64,12 @@
</td>
</ng-container>
<ng-container matColumnDef="activityCount">
<ng-container matColumnDef="activitiesCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Activity Count</ng-container>
<ng-container i18n>Activities Count</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.activityCount }}
{{ element.activitiesCount }}
</td>
</ng-container>
@ -82,21 +82,21 @@
</td>
</ng-container>
<ng-container matColumnDef="countriesCount">
<ng-container matColumnDef="sectorsCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Countries Count</ng-container>
<ng-container i18n>Sectors Count</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.countriesCount }}
{{ element.sectorsCount }}
</td>
</ng-container>
<ng-container matColumnDef="sectorsCount">
<ng-container matColumnDef="countriesCount">
<th *matHeaderCellDef class="px-1" mat-header-cell mat-sort-header>
<ng-container i18n>Sectors Count</ng-container>
<ng-container i18n>Countries Count</ng-container>
</th>
<td *matCellDef="let element" class="px-1 text-right" mat-cell>
{{ element.sectorsCount }}
{{ element.countriesCount }}
</td>
</ng-container>
@ -146,7 +146,7 @@
</button>
<button
mat-menu-item
[disabled]="element.activityCount !== 0"
[disabled]="element.activitiesCount !== 0"
(click)="onDeleteProfileData({dataSource: element.dataSource, symbol: element.symbol})"
>
<ng-container i18n>Delete</ng-container>

@ -8,7 +8,10 @@ import {
} from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { AdminService } from '@ghostfolio/client/services/admin.service';
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import {
EnhancedSymbolProfile,
UniqueAsset
} from '@ghostfolio/common/interfaces';
import { MarketData } from '@prisma/client';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@ -23,7 +26,14 @@ import { AssetProfileDialogParams } from './interfaces/interfaces';
styleUrls: ['./asset-profile-dialog.component.scss']
})
export class AssetProfileDialog implements OnDestroy, OnInit {
public assetProfile: EnhancedSymbolProfile;
public countries: {
[code: string]: { name: string; value: number };
};
public marketDataDetails: MarketData[] = [];
public sectors: {
[name: string]: { name: string; value: number };
};
private unsubscribeSubject = new Subject<void>();
@ -57,8 +67,29 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
this.adminService
.fetchAdminMarketDataBySymbol({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketData }) => {
.subscribe(({ assetProfile, marketData }) => {
this.assetProfile = assetProfile;
this.countries = {};
this.marketDataDetails = marketData;
this.sectors = {};
if (assetProfile?.countries?.length > 0) {
for (const country of assetProfile.countries) {
this.countries[country.code] = {
name: country.name,
value: country.weight
};
}
}
if (assetProfile?.sectors?.length > 0) {
for (const sector of assetProfile.sectors) {
this.sectors[sector.name] = {
name: sector.name,
value: sector.weight
};
}
}
this.changeDetectorRef.markForCheck();
});

@ -2,12 +2,13 @@
mat-dialog-title
position="center"
[deviceType]="data.deviceType"
[title]="data.symbol"
[title]="assetProfile?.name ?? data.symbol"
(closeButtonClicked)="onClose()"
></gf-dialog-header>
<div class="flex-grow-1" mat-dialog-content>
<gf-admin-market-data-detail
class="mb-3"
[dataSource]="data.dataSource"
[dateOfFirstActivity]="data.dateOfFirstActivity"
[locale]="data.locale"
@ -15,6 +16,93 @@
[symbol]="data.symbol"
(marketDataChanged)="onMarketDataChanged($event)"
></gf-admin-market-data-detail>
<div class="row">
<div class="col-6 mb-3">
<gf-value
i18n
size="medium"
[isDate]="data.dateOfFirstActivity ? true : false"
[locale]="data.locale"
[value]="data.dateOfFirstActivity ?? '-'"
>First Buy Date</gf-value
>
</div>
<div class="col-6 mb-3">
<gf-value
i18n
size="medium"
[locale]="data.locale"
[value]="assetProfile?.activitiesCount ?? 0"
>Transactions</gf-value
>
</div>
<div class="col-6 mb-3">
<gf-value
i18n
size="medium"
[hidden]="!assetProfile?.assetClass"
[value]="assetProfile?.assetClass"
>Asset Class</gf-value
>
</div>
<div class="col-6 mb-3">
<gf-value
i18n
size="medium"
[hidden]="!assetProfile?.assetSubClass"
[value]="assetProfile?.assetSubClass"
>Asset Sub Class</gf-value
>
</div>
<ng-container
*ngIf="assetProfile?.countries?.length > 0 || assetProfile?.sectors?.length > 0"
>
<ng-container
*ngIf="assetProfile?.countries?.length === 1 && assetProfile?.sectors?.length === 1; else charts"
>
<div *ngIf="assetProfile?.sectors?.length === 1" class="col-6 mb-3">
<gf-value
i18n
size="medium"
[locale]="data.locale"
[value]="assetProfile?.sectors[0].name"
>Sector</gf-value
>
</div>
<div *ngIf="assetProfile?.countries?.length === 1" class="col-6 mb-3">
<gf-value
i18n
size="medium"
[locale]="data.locale"
[value]="assetProfile?.countries[0].name"
>Country</gf-value
>
</div>
</ng-container>
<ng-template #charts>
<div class="col-md-6 mb-3">
<div class="h5" i18n>Sectors</div>
<gf-portfolio-proportion-chart
[colorScheme]="data.colorScheme"
[isInPercent]="true"
[keys]="['name']"
[maxItems]="10"
[positions]="sectors"
></gf-portfolio-proportion-chart>
</div>
<div class="col-md-6 mb-3">
<div class="h5" i18n>Countries</div>
<gf-portfolio-proportion-chart
[colorScheme]="data.colorScheme"
[isInPercent]="true"
[keys]="['name']"
[maxItems]="10"
[positions]="countries"
></gf-portfolio-proportion-chart>
</div>
</ng-template>
</ng-container>
</div>
</div>
<gf-dialog-footer

@ -5,6 +5,8 @@ import { MatDialogModule } from '@angular/material/dialog';
import { GfAdminMarketDataDetailModule } from '@ghostfolio/client/components/admin-market-data-detail/admin-market-data-detail.module';
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value';
import { AssetProfileDialog } from './asset-profile-dialog.component';
@ -15,6 +17,8 @@ import { AssetProfileDialog } from './asset-profile-dialog.component';
GfAdminMarketDataDetailModule,
GfDialogFooterModule,
GfDialogHeaderModule,
GfPortfolioProportionChartModule,
GfValueModule,
MatButtonModule,
MatDialogModule
],

@ -1,5 +1,8 @@
import { MarketData } from '@prisma/client';
import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface';
export interface AdminMarketDataDetails {
assetProfile: EnhancedSymbolProfile;
marketData: MarketData[];
}

@ -5,6 +5,7 @@ import { ScraperConfiguration } from './scraper-configuration.interface';
import { Sector } from './sector.interface';
export interface EnhancedSymbolProfile {
activitiesCount: number;
assetClass: AssetClass;
assetSubClass: AssetSubClass;
countries: Country[];

Loading…
Cancel
Save