Feature/extend scraper configuration support (#2110)

* Extend scraper configuration support

* Update changelog
This commit is contained in:
Thomas Kaul 2023-07-01 11:08:10 +02:00 committed by GitHub
parent b7f635bdfc
commit e62da06c5c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 50 additions and 5 deletions

View File

@ -10,6 +10,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added pagination to the historical market data table of the admin control panel - Added pagination to the historical market data table of the admin control panel
- Added the attribute `headers` to the scraper configuration
### Changed
- Extended the asset profile details dialog in the admin control panel by the scraper configuration
## 1.284.0 - 2023-06-27 ## 1.284.0 - 2023-06-27

View File

@ -249,12 +249,14 @@ export class AdminService {
public async patchAssetProfileData({ public async patchAssetProfileData({
comment, comment,
dataSource, dataSource,
scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) { }: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
await this.symbolProfileService.updateSymbolProfile({ await this.symbolProfileService.updateSymbolProfile({
comment, comment,
dataSource, dataSource,
scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping
}); });

View File

@ -1,3 +1,4 @@
import { Prisma } from '@prisma/client';
import { IsObject, IsOptional, IsString } from 'class-validator'; import { IsObject, IsOptional, IsString } from 'class-validator';
export class UpdateAssetProfileDto { export class UpdateAssetProfileDto {
@ -5,6 +6,10 @@ export class UpdateAssetProfileDto {
@IsOptional() @IsOptional()
comment?: string; comment?: string;
@IsObject()
@IsOptional()
scraperConfiguration?: Prisma.InputJsonObject;
@IsObject() @IsObject()
@IsOptional() @IsOptional()
symbolMapping?: { symbolMapping?: {

View File

@ -67,8 +67,12 @@ export class ManualService implements DataProviderInterface {
const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles( const [symbolProfile] = await this.symbolProfileService.getSymbolProfiles(
[{ symbol, dataSource: this.getName() }] [{ symbol, dataSource: this.getName() }]
); );
const { defaultMarketPrice, selector, url } = const {
symbolProfile.scraperConfiguration ?? {}; defaultMarketPrice,
headers = {},
selector,
url
} = symbolProfile.scraperConfiguration ?? {};
if (defaultMarketPrice) { if (defaultMarketPrice) {
const historical: { const historical: {
@ -91,7 +95,7 @@ export class ManualService implements DataProviderInterface {
return {}; return {};
} }
const get = bent(url, 'GET', 'string', 200, {}); const get = bent(url, 'GET', 'string', 200, headers);
const html = await get(); const html = await get();
const $ = cheerio.load(html); const $ = cheerio.load(html);

View File

@ -96,11 +96,12 @@ export class SymbolProfileService {
public updateSymbolProfile({ public updateSymbolProfile({
comment, comment,
dataSource, dataSource,
scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping
}: Prisma.SymbolProfileUpdateInput & UniqueAsset) { }: Prisma.SymbolProfileUpdateInput & UniqueAsset) {
return this.prismaService.symbolProfile.update({ return this.prismaService.symbolProfile.update({
data: { comment, symbolMapping }, data: { comment, scraperConfiguration, symbolMapping },
where: { dataSource_symbol: { dataSource, symbol } } where: { dataSource_symbol: { dataSource, symbol } }
}); });
} }
@ -195,6 +196,8 @@ export class SymbolProfileService {
if (scraperConfiguration) { if (scraperConfiguration) {
return { return {
defaultMarketPrice: scraperConfiguration.defaultMarketPrice as number, defaultMarketPrice: scraperConfiguration.defaultMarketPrice as number,
headers:
scraperConfiguration.headers as ScraperConfiguration['headers'],
selector: scraperConfiguration.selector as string, selector: scraperConfiguration.selector as string,
url: scraperConfiguration.url as string url: scraperConfiguration.url as string
}; };

View File

@ -13,6 +13,7 @@ import { AdminService } from '@ghostfolio/client/services/admin.service';
import { DataService } from '@ghostfolio/client/services/data.service'; import { DataService } from '@ghostfolio/client/services/data.service';
import { import {
AdminMarketDataDetails, AdminMarketDataDetails,
ScraperConfiguration,
UniqueAsset UniqueAsset
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { translate } from '@ghostfolio/ui/i18n'; import { translate } from '@ghostfolio/ui/i18n';
@ -34,6 +35,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
public assetProfile: AdminMarketDataDetails['assetProfile']; public assetProfile: AdminMarketDataDetails['assetProfile'];
public assetProfileForm = this.formBuilder.group({ public assetProfileForm = this.formBuilder.group({
comment: '', comment: '',
scraperConfiguration: '',
symbolMapping: '' symbolMapping: ''
}); });
public assetSubClass: string; public assetSubClass: string;
@ -103,6 +105,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
this.assetProfileForm.setValue({ this.assetProfileForm.setValue({
comment: this.assetProfile?.comment ?? '', comment: this.assetProfile?.comment ?? '',
scraperConfiguration: JSON.stringify(
this.assetProfile?.scraperConfiguration ?? {}
),
symbolMapping: JSON.stringify(this.assetProfile?.symbolMapping ?? {}) symbolMapping: JSON.stringify(this.assetProfile?.symbolMapping ?? {})
}); });
@ -148,8 +153,15 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
} }
public onSubmit() { public onSubmit() {
let scraperConfiguration = {};
let symbolMapping = {}; let symbolMapping = {};
try {
scraperConfiguration = JSON.parse(
this.assetProfileForm.controls['scraperConfiguration'].value
);
} catch {}
try { try {
symbolMapping = JSON.parse( symbolMapping = JSON.parse(
this.assetProfileForm.controls['symbolMapping'].value this.assetProfileForm.controls['symbolMapping'].value
@ -157,6 +169,7 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
} catch {} } catch {}
const assetProfileData: UpdateAssetProfileDto = { const assetProfileData: UpdateAssetProfileDto = {
scraperConfiguration,
symbolMapping, symbolMapping,
comment: this.assetProfileForm.controls['comment'].value ?? null comment: this.assetProfileForm.controls['comment'].value ?? null
}; };

View File

@ -162,6 +162,17 @@
></textarea> ></textarea>
</mat-form-field> </mat-form-field>
</div> </div>
<div *ngIf="assetProfile?.dataSource === 'MANUAL'">
<mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Scraper Configuration</mat-label>
<textarea
cdkTextareaAutosize
formControlName="scraperConfiguration"
matInput
type="text"
></textarea>
</mat-form-field>
</div>
<div> <div>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Note</mat-label> <mat-label i18n>Note</mat-label>

View File

@ -191,12 +191,13 @@ export class AdminService {
public patchAssetProfile({ public patchAssetProfile({
comment, comment,
dataSource, dataSource,
scraperConfiguration,
symbol, symbol,
symbolMapping symbolMapping
}: UniqueAsset & UpdateAssetProfileDto) { }: UniqueAsset & UpdateAssetProfileDto) {
return this.http.patch<EnhancedSymbolProfile>( return this.http.patch<EnhancedSymbolProfile>(
`/api/v1/admin/profile-data/${dataSource}/${symbol}`, `/api/v1/admin/profile-data/${dataSource}/${symbol}`,
{ comment, symbolMapping } { comment, scraperConfiguration, symbolMapping }
); );
} }

View File

@ -1,5 +1,6 @@
export interface ScraperConfiguration { export interface ScraperConfiguration {
defaultMarketPrice?: number; defaultMarketPrice?: number;
headers?: { [key: string]: string };
selector: string; selector: string;
url: string; url: string;
} }