Browse Source

Merge branch 'main' into task/improve-styling-in-user-detail-dialog-of-admin-control

pull/6992/head
Thomas Kaul 3 days ago
committed by GitHub
parent
commit
34ded398f1
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      CHANGELOG.md
  2. 56
      apps/api/src/app/admin/admin.service.ts
  3. 29
      apps/api/src/services/queues/data-gathering/data-gathering.service.ts
  4. 58
      apps/api/src/services/symbol-profile/symbol-profile.service.ts
  5. 45
      libs/common/src/lib/helper.ts

2
CHANGELOG.md

@ -13,12 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Centralized the asset profile override logic for manual adjustments
- Improved the styling in the user detail dialog of the admin control panel’s users section - Improved the styling in the user detail dialog of the admin control panel’s users section
- Refactored the backend logging to use the instance-based `Logger` - Refactored the backend logging to use the instance-based `Logger`
- Improved the language localization for Ukrainian (`uk`) - Improved the language localization for Ukrainian (`uk`)
### Fixed ### Fixed
- Fixed an issue where the asset profile override (asset class and asset sub class) was not applied to the data enhancers when gathering asset profiles
- Fixed a layout issue in the asset profile dialog of the admin control panel by truncating long titles - Fixed a layout issue in the asset profile dialog of the admin control panel by truncating long titles
## 3.7.0 - 2026-06-02 ## 3.7.0 - 2026-06-02

56
apps/api/src/app/admin/admin.service.ts

@ -14,6 +14,7 @@ import {
PROPERTY_IS_USER_SIGNUP_ENABLED PROPERTY_IS_USER_SIGNUP_ENABLED
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { import {
applyAssetProfileOverrides,
getAssetProfileIdentifier, getAssetProfileIdentifier,
getCurrencyFromSymbol, getCurrencyFromSymbol,
isCurrency isCurrency
@ -29,7 +30,6 @@ import {
EnhancedSymbolProfile, EnhancedSymbolProfile,
Filter Filter
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { Sector } from '@ghostfolio/common/interfaces/sector.interface';
import { MarketDataPreset } from '@ghostfolio/common/types'; import { MarketDataPreset } from '@ghostfolio/common/types';
import { import {
@ -349,25 +349,26 @@ export class AdminService {
} }
let marketData: AdminMarketDataItem[] = await Promise.all( let marketData: AdminMarketDataItem[] = await Promise.all(
assetProfiles.map( assetProfiles.map(async (assetProfile) => {
async ({ const {
_count, _count,
activities, activities,
assetClass,
assetSubClass,
comment, comment,
countries,
currency, currency,
dataSource, dataSource,
id, id,
isActive, isActive,
isUsedByUsersWithSubscription, isUsedByUsersWithSubscription,
name, symbol
sectors, } = assetProfile;
symbol,
SymbolProfileOverrides const { assetClass, assetSubClass, countries, name, sectors } =
}) => { applyAssetProfileOverrides(
let countriesCount = countries ? Object.keys(countries).length : 0; assetProfile,
assetProfile.SymbolProfileOverrides
);
const countriesCount = countries ? Object.keys(countries).length : 0;
const lastMarketPrice = lastMarketPriceMap.get( const lastMarketPrice = lastMarketPriceMap.get(
getAssetProfileIdentifier({ dataSource, symbol }) getAssetProfileIdentifier({ dataSource, symbol })
@ -381,33 +382,7 @@ export class AdminService {
); );
})?._count ?? 0; })?._count ?? 0;
let sectorsCount = sectors ? Object.keys(sectors).length : 0; const sectorsCount = sectors ? Object.keys(sectors).length : 0;
if (SymbolProfileOverrides) {
assetClass = SymbolProfileOverrides.assetClass ?? assetClass;
assetSubClass =
SymbolProfileOverrides.assetSubClass ?? assetSubClass;
if (
(SymbolProfileOverrides.countries as unknown as Prisma.JsonArray)
?.length > 0
) {
countriesCount = (
SymbolProfileOverrides.countries as unknown as Prisma.JsonArray
).length;
}
name = SymbolProfileOverrides.name ?? name;
if (
(SymbolProfileOverrides.sectors as unknown as Sector[])?.length >
0
) {
sectorsCount = (
SymbolProfileOverrides.sectors as unknown as Prisma.JsonArray
).length;
}
}
return { return {
assetClass, assetClass,
@ -428,8 +403,7 @@ export class AdminService {
isUsedByUsersWithSubscription: await isUsedByUsersWithSubscription, isUsedByUsersWithSubscription: await isUsedByUsersWithSubscription,
watchedByCount: _count.watchedBy watchedByCount: _count.watchedBy
}; };
} })
)
); );
if (presetId) { if (presetId) {

29
apps/api/src/services/queues/data-gathering/data-gathering.service.ts

@ -178,14 +178,27 @@ export class DataGatheringService {
); );
for (const [symbol, assetProfile] of Object.entries(assetProfiles)) { for (const [symbol, assetProfile] of Object.entries(assetProfiles)) {
const symbolMapping = symbolProfiles.find((symbolProfile) => { const symbolProfile = symbolProfiles.find(
return symbolProfile.symbol === symbol; ({ symbol: symbolProfileSymbol }) => {
})?.symbolMapping; return symbolProfileSymbol === symbol;
}
);
const symbolMapping = symbolProfile?.symbolMapping;
let enhancedAssetProfile = symbolProfile
? {
...assetProfile,
assetClass: symbolProfile.assetClass ?? assetProfile.assetClass,
assetSubClass:
symbolProfile.assetSubClass ?? assetProfile.assetSubClass
}
: assetProfile;
for (const dataEnhancer of this.dataEnhancers) { for (const dataEnhancer of this.dataEnhancers) {
try { try {
assetProfiles[symbol] = await dataEnhancer.enhance({ enhancedAssetProfile = await dataEnhancer.enhance({
response: assetProfile, response: enhancedAssetProfile,
symbol: symbolMapping?.[dataEnhancer.getName()] ?? symbol symbol: symbolMapping?.[dataEnhancer.getName()] ?? symbol
}); });
} catch (error) { } catch (error) {
@ -198,9 +211,9 @@ export class DataGatheringService {
} }
} }
const { assetClass, assetSubClass } = assetProfile;
const { const {
assetClass,
assetSubClass,
countries, countries,
currency, currency,
cusip, cusip,
@ -213,7 +226,7 @@ export class DataGatheringService {
name, name,
sectors, sectors,
url url
} = assetProfile; } = enhancedAssetProfile;
try { try {
await this.prismaService.symbolProfile.upsert({ await this.prismaService.symbolProfile.upsert({

58
apps/api/src/services/symbol-profile/symbol-profile.service.ts

@ -1,5 +1,6 @@
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { applyAssetProfileOverrides } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
EnhancedSymbolProfile, EnhancedSymbolProfile,
@ -192,21 +193,28 @@ export class SymbolProfileService {
})[] })[]
): EnhancedSymbolProfile[] { ): EnhancedSymbolProfile[] {
return symbolProfiles.map((symbolProfile) => { return symbolProfiles.map((symbolProfile) => {
const symbolProfileWithOverrides = applyAssetProfileOverrides(
symbolProfile,
symbolProfile.SymbolProfileOverrides
);
const item = { const item = {
...symbolProfile, ...symbolProfileWithOverrides,
activitiesCount: 0, activitiesCount: 0,
countries: this.getCountries( countries: this.getCountries(
symbolProfile?.countries as unknown as Prisma.JsonArray symbolProfileWithOverrides?.countries as unknown as Prisma.JsonArray
), ),
dateOfFirstActivity: undefined as Date, dateOfFirstActivity: undefined as Date,
holdings: this.getHoldings( holdings: this.getHoldings(
symbolProfile?.holdings as unknown as Prisma.JsonArray symbolProfileWithOverrides?.holdings as unknown as Prisma.JsonArray
),
scraperConfiguration: this.getScraperConfiguration(
symbolProfileWithOverrides
), ),
scraperConfiguration: this.getScraperConfiguration(symbolProfile),
sectors: this.getSectors( sectors: this.getSectors(
symbolProfile?.sectors as unknown as Prisma.JsonArray symbolProfileWithOverrides?.sectors as unknown as Prisma.JsonArray
), ),
symbolMapping: this.getSymbolMapping(symbolProfile), symbolMapping: this.getSymbolMapping(symbolProfileWithOverrides),
watchedByCount: 0 watchedByCount: 0
}; };
@ -217,45 +225,7 @@ export class SymbolProfileService {
item.dateOfFirstActivity = symbolProfile.activities?.[0]?.date; item.dateOfFirstActivity = symbolProfile.activities?.[0]?.date;
delete item.activities; delete item.activities;
if (item.SymbolProfileOverrides) {
item.assetClass =
item.SymbolProfileOverrides.assetClass ?? item.assetClass;
item.assetSubClass =
item.SymbolProfileOverrides.assetSubClass ?? item.assetSubClass;
if (
(item.SymbolProfileOverrides.countries as unknown as Prisma.JsonArray)
?.length > 0
) {
item.countries = this.getCountries(
item.SymbolProfileOverrides.countries as unknown as Prisma.JsonArray
);
}
if (
(item.SymbolProfileOverrides.holdings as unknown as Holding[])
?.length > 0
) {
item.holdings = this.getHoldings(
item.SymbolProfileOverrides.holdings as unknown as Prisma.JsonArray
);
}
item.name = item.SymbolProfileOverrides.name ?? item.name;
if (
(item.SymbolProfileOverrides.sectors as unknown as Sector[])?.length >
0
) {
item.sectors = this.getSectors(
item.SymbolProfileOverrides.sectors as unknown as Prisma.JsonArray
);
}
item.url = item.SymbolProfileOverrides.url ?? item.url;
delete item.SymbolProfileOverrides; delete item.SymbolProfileOverrides;
}
return item; return item;
}); });

45
libs/common/src/lib/helper.ts

@ -1,5 +1,12 @@
import { NumberParser } from '@internationalized/number'; import { NumberParser } from '@internationalized/number';
import { Type as ActivityType, DataSource, MarketData } from '@prisma/client'; import {
Type as ActivityType,
DataSource,
MarketData,
Prisma,
SymbolProfile,
SymbolProfileOverrides
} from '@prisma/client';
import { Big } from 'big.js'; import { Big } from 'big.js';
import { isISO4217CurrencyCode } from 'class-validator'; import { isISO4217CurrencyCode } from 'class-validator';
import { import {
@ -47,6 +54,42 @@ export const DATE_FORMAT = 'yyyy-MM-dd';
export const DATE_FORMAT_MONTHLY = 'MMMM yyyy'; export const DATE_FORMAT_MONTHLY = 'MMMM yyyy';
export const DATE_FORMAT_YEARLY = 'yyyy'; export const DATE_FORMAT_YEARLY = 'yyyy';
export function applyAssetProfileOverrides<T extends Partial<SymbolProfile>>(
assetProfile: T,
assetProfileOverrides: SymbolProfileOverrides | null
): T {
if (!assetProfileOverrides) {
return assetProfile;
}
const assetProfileWithOverrides = { ...assetProfile } as T;
assetProfileWithOverrides.assetClass =
assetProfileOverrides.assetClass ?? assetProfile.assetClass;
assetProfileWithOverrides.assetSubClass =
assetProfileOverrides.assetSubClass ?? assetProfile.assetSubClass;
if ((assetProfileOverrides.countries as Prisma.JsonArray)?.length > 0) {
assetProfileWithOverrides.countries = assetProfileOverrides.countries;
}
if ((assetProfileOverrides.holdings as Prisma.JsonArray)?.length > 0) {
assetProfileWithOverrides.holdings = assetProfileOverrides.holdings;
}
assetProfileWithOverrides.name =
assetProfileOverrides.name ?? assetProfile.name;
if ((assetProfileOverrides.sectors as Prisma.JsonArray)?.length > 0) {
assetProfileWithOverrides.sectors = assetProfileOverrides.sectors;
}
assetProfileWithOverrides.url = assetProfileOverrides.url ?? assetProfile.url;
return assetProfileWithOverrides;
}
export function calculateBenchmarkTrend({ export function calculateBenchmarkTrend({
days, days,
historicalData historicalData

Loading…
Cancel
Save