Browse Source

Fix creation of asset profiles with symbol in wrong letter case by using original symbol

pull/7727/head
Thomas Kaul 2 days ago
parent
commit
5db792417a
  1. 5
      apps/api/src/app/activities/activities.controller.ts
  2. 1
      apps/api/src/app/activities/activities.service.ts
  3. 21
      apps/api/src/services/queues/data-gathering/data-gathering.service.spec.ts
  4. 4
      apps/api/src/services/queues/data-gathering/data-gathering.service.ts
  5. 35
      apps/api/src/services/symbol-profile/symbol-profile.service.ts
  6. 17
      apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

5
apps/api/src/app/activities/activities.controller.ts

@ -229,7 +229,7 @@ export class ActivitiesController {
let assetProfiles: {
[assetProfileIdentifier: string]: Partial<SymbolProfile>;
} = {};
};
try {
assetProfiles = await this.dataProviderService.validateActivities({
@ -382,8 +382,7 @@ export class ActivitiesController {
},
update: {
assetClass: data.assetClass,
assetSubClass: data.assetSubClass,
name: data.symbol
assetSubClass: data.assetSubClass
}
},
tags: data.tags?.map((id) => {

1
apps/api/src/app/activities/activities.service.ts

@ -896,7 +896,6 @@ export class ActivitiesService {
data.type === 'BUY')
) {
delete data.SymbolProfile.connect;
delete data.SymbolProfile.update.name;
} else {
delete data.SymbolProfile.update;

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

@ -155,6 +155,27 @@ describe('DataGatheringService', () => {
})
);
});
it('Creates a new asset profile with the symbol of the data provider', async () => {
dataProviderService.getAssetProfiles.mockResolvedValue({
'YAHOO-aapl': {
currency: 'USD',
dataSource: DataSource.YAHOO,
name: 'Apple Inc.',
symbol: 'AAPL'
}
});
await dataGatheringService.gatherAssetProfiles([
{ dataSource: DataSource.YAHOO, symbol: 'aapl' }
]);
expect(prismaService.symbolProfile.upsert).toHaveBeenCalledWith(
expect.objectContaining({
create: expect.objectContaining({ symbol: 'AAPL' })
})
);
});
});
describe('gatherRecentMarketData', () => {

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

@ -166,8 +166,8 @@ export class DataGatheringService {
isin,
name,
sectors,
symbol,
url
url,
symbol: assetProfile.symbol
},
update: {
assetClass,

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

@ -143,14 +143,29 @@ export class SymbolProfileService {
return symbol;
}
const symbolProfile = await this.prismaService.symbolProfile.findFirst({
where: {
dataSource,
symbol: { equals: symbol, mode: 'insensitive' }
}
const symbolProfile = await this.prismaService.symbolProfile.findUnique({
where: { dataSource_symbol: { dataSource, symbol } }
});
return symbolProfile?.symbol ?? symbolOfDataProvider ?? symbol;
if (symbolProfile) {
return symbolProfile.symbol;
}
const symbolProfileWithOtherLetterCase =
await this.prismaService.symbolProfile.findFirst({
orderBy: { symbol: 'asc' },
where: {
dataSource,
symbol: {
equals: this.escapeLikePattern(symbol),
mode: 'insensitive'
}
}
});
return (
symbolProfileWithOtherLetterCase?.symbol ?? symbolOfDataProvider ?? symbol
);
}
public async getSymbolProfiles(
@ -315,6 +330,14 @@ export class SymbolProfileService {
});
}
/**
* Escapes the wildcard characters of a LIKE pattern, because Prisma
* translates a case-insensitive filter into an ILIKE expression.
*/
private escapeLikePattern(value: string) {
return value.replace(/[\\%_]/g, '\\$&');
}
private getCountries(aCountries: Prisma.JsonArray = []): Country[] {
if (aCountries === null) {
return [];

17
apps/client/src/app/components/admin-market-data/admin-market-data.component.ts

@ -22,6 +22,7 @@ import { GfValueComponent } from '@ghostfolio/ui/value';
import { SelectionModel } from '@angular/cdk/collections';
import { CommonModule } from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import {
AfterViewInit,
ChangeDetectionStrategy,
@ -497,7 +498,21 @@ export class GfAdminMarketDataComponent implements AfterViewInit, OnInit {
.addAssetProfile({ dataSource, symbol })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
error: () => {
error: (error: HttpErrorResponse) => {
const { message } = (error.error ?? {}) as {
message?: string;
};
this.snackBar.open(
'😞 ' +
(message ??
$localize`An error occurred while creating the asset profile ${symbol} (${dataSource}).`),
undefined,
{
duration: ms('3 seconds')
}
);
this.router.navigate(['.'], { relativeTo: this.route });
},
next: (assetProfile) => {

Loading…
Cancel
Save