Feature/provide data provider info in search (#2958)
* Provide data provider info in search * Update changelog
This commit is contained in:
parent
06ba7a4b1b
commit
893e76f83f
@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Extended the assistant by an asset class selector (experimental)
|
||||
- Added the data provider information to the search endpoint
|
||||
|
||||
### Changed
|
||||
|
||||
|
@ -64,16 +64,13 @@ export class ImportController {
|
||||
maxActivitiesToImport = Number.MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
const userCurrency = this.request.user.Settings.settings.baseCurrency;
|
||||
|
||||
try {
|
||||
const activities = await this.importService.import({
|
||||
isDryRun,
|
||||
maxActivitiesToImport,
|
||||
userCurrency,
|
||||
accountsDto: importData.accounts ?? [],
|
||||
activitiesDto: importData.activities,
|
||||
userId: this.request.user.id
|
||||
user: this.request.user
|
||||
});
|
||||
|
||||
return { activities };
|
||||
|
@ -21,7 +21,8 @@ import {
|
||||
import { UniqueAsset } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
AccountWithPlatform,
|
||||
OrderWithAccount
|
||||
OrderWithAccount,
|
||||
UserWithSettings
|
||||
} from '@ghostfolio/common/types';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, Prisma, SymbolProfile } from '@prisma/client';
|
||||
@ -138,17 +139,16 @@ export class ImportService {
|
||||
activitiesDto,
|
||||
isDryRun = false,
|
||||
maxActivitiesToImport,
|
||||
userCurrency,
|
||||
userId
|
||||
user
|
||||
}: {
|
||||
accountsDto: Partial<CreateAccountDto>[];
|
||||
activitiesDto: Partial<CreateOrderDto>[];
|
||||
isDryRun?: boolean;
|
||||
maxActivitiesToImport: number;
|
||||
userCurrency: string;
|
||||
userId: string;
|
||||
user: UserWithSettings;
|
||||
}): Promise<Activity[]> {
|
||||
const accountIdMapping: { [oldAccountId: string]: string } = {};
|
||||
const userCurrency = user.Settings.settings.baseCurrency;
|
||||
|
||||
if (!isDryRun && accountsDto?.length) {
|
||||
const [existingAccounts, existingPlatforms] = await Promise.all([
|
||||
@ -171,7 +171,7 @@ export class ImportService {
|
||||
);
|
||||
|
||||
// If there is no account or if the account belongs to a different user then create a new account
|
||||
if (!accountWithSameId || accountWithSameId.userId !== userId) {
|
||||
if (!accountWithSameId || accountWithSameId.userId !== user.id) {
|
||||
let oldAccountId: string;
|
||||
const platformId = account.platformId;
|
||||
|
||||
@ -184,7 +184,7 @@ export class ImportService {
|
||||
|
||||
let accountObject: Prisma.AccountCreateInput = {
|
||||
...account,
|
||||
User: { connect: { id: userId } }
|
||||
User: { connect: { id: user.id } }
|
||||
};
|
||||
|
||||
if (
|
||||
@ -200,7 +200,7 @@ export class ImportService {
|
||||
|
||||
const newAccount = await this.accountService.createAccount(
|
||||
accountObject,
|
||||
userId
|
||||
user.id
|
||||
);
|
||||
|
||||
// Store the new to old account ID mappings for updating activities
|
||||
@ -231,16 +231,17 @@ export class ImportService {
|
||||
|
||||
const assetProfiles = await this.validateActivities({
|
||||
activitiesDto,
|
||||
maxActivitiesToImport
|
||||
maxActivitiesToImport,
|
||||
user
|
||||
});
|
||||
|
||||
const activitiesExtendedWithErrors = await this.extendActivitiesWithErrors({
|
||||
activitiesDto,
|
||||
userCurrency,
|
||||
userId
|
||||
userId: user.id
|
||||
});
|
||||
|
||||
const accounts = (await this.accountService.getAccounts(userId)).map(
|
||||
const accounts = (await this.accountService.getAccounts(user.id)).map(
|
||||
({ id, name }) => {
|
||||
return { id, name };
|
||||
}
|
||||
@ -345,7 +346,6 @@ export class ImportService {
|
||||
quantity,
|
||||
type,
|
||||
unitPrice,
|
||||
userId,
|
||||
accountId: validatedAccount?.id,
|
||||
accountUserId: undefined,
|
||||
createdAt: new Date(),
|
||||
@ -374,7 +374,8 @@ export class ImportService {
|
||||
},
|
||||
Account: validatedAccount,
|
||||
symbolProfileId: undefined,
|
||||
updatedAt: new Date()
|
||||
updatedAt: new Date(),
|
||||
userId: user.id
|
||||
};
|
||||
} else {
|
||||
if (error) {
|
||||
@ -388,7 +389,6 @@ export class ImportService {
|
||||
quantity,
|
||||
type,
|
||||
unitPrice,
|
||||
userId,
|
||||
accountId: validatedAccount?.id,
|
||||
SymbolProfile: {
|
||||
connectOrCreate: {
|
||||
@ -406,7 +406,8 @@ export class ImportService {
|
||||
}
|
||||
},
|
||||
updateAccountBalance: false,
|
||||
User: { connect: { id: userId } }
|
||||
User: { connect: { id: user.id } },
|
||||
userId: user.id
|
||||
});
|
||||
}
|
||||
|
||||
@ -553,10 +554,12 @@ export class ImportService {
|
||||
|
||||
private async validateActivities({
|
||||
activitiesDto,
|
||||
maxActivitiesToImport
|
||||
maxActivitiesToImport,
|
||||
user
|
||||
}: {
|
||||
activitiesDto: Partial<CreateOrderDto>[];
|
||||
maxActivitiesToImport: number;
|
||||
user: UserWithSettings;
|
||||
}) {
|
||||
if (activitiesDto?.length > maxActivitiesToImport) {
|
||||
throw new Error(`Too many activities (${maxActivitiesToImport} at most)`);
|
||||
@ -583,6 +586,21 @@ export class ImportService {
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
||||
user.subscription.type === 'Basic'
|
||||
) {
|
||||
const dataProvider = this.dataProviderService.getDataProvider(
|
||||
DataSource[dataSource]
|
||||
);
|
||||
|
||||
if (dataProvider.getDataProviderInfo().isPremium) {
|
||||
throw new Error(
|
||||
`activities.${index}.dataSource ("${dataSource}") is not valid`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const assetProfile = {
|
||||
currency,
|
||||
...(
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { AssetClass, AssetSubClass, DataSource } from '@prisma/client';
|
||||
|
||||
export interface LookupItem {
|
||||
assetClass: AssetClass;
|
||||
assetSubClass: AssetSubClass;
|
||||
currency: string;
|
||||
dataProviderInfo: DataProviderInfo;
|
||||
dataSource: DataSource;
|
||||
name: string;
|
||||
symbol: string;
|
||||
|
@ -12,6 +12,7 @@ import {
|
||||
IDataProviderResponse
|
||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import * as Alphavantage from 'alphavantage';
|
||||
@ -44,6 +45,12 @@ export class AlphaVantageService implements DataProviderInterface {
|
||||
};
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: false
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({}: GetDividendsParams) {
|
||||
return {};
|
||||
}
|
||||
@ -118,6 +125,7 @@ export class AlphaVantageService implements DataProviderInterface {
|
||||
assetClass: undefined,
|
||||
assetSubClass: undefined,
|
||||
currency: bestMatch['8. currency'],
|
||||
dataProviderInfo: this.getDataProviderInfo(),
|
||||
dataSource: this.getName(),
|
||||
name: bestMatch['2. name'],
|
||||
symbol: bestMatch['1. symbol']
|
||||
|
@ -91,6 +91,14 @@ export class CoinGeckoService implements DataProviderInterface {
|
||||
return response;
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: false,
|
||||
name: 'CoinGecko',
|
||||
url: 'https://coingecko.com'
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({}: GetDividendsParams) {
|
||||
return {};
|
||||
}
|
||||
@ -252,11 +260,4 @@ export class CoinGeckoService implements DataProviderInterface {
|
||||
|
||||
return { items };
|
||||
}
|
||||
|
||||
private getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
name: 'CoinGecko',
|
||||
url: 'https://coingecko.com'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,31 @@ export class DataProviderService {
|
||||
return response;
|
||||
}
|
||||
|
||||
public getDataProvider(providerName: DataSource) {
|
||||
for (const dataProviderInterface of this.dataProviderInterfaces) {
|
||||
if (this.dataProviderMapping[dataProviderInterface.getName()]) {
|
||||
const mappedDataProviderInterface = this.dataProviderInterfaces.find(
|
||||
(currentDataProviderInterface) => {
|
||||
return (
|
||||
currentDataProviderInterface.getName() ===
|
||||
this.dataProviderMapping[dataProviderInterface.getName()]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (mappedDataProviderInterface) {
|
||||
return mappedDataProviderInterface;
|
||||
}
|
||||
}
|
||||
|
||||
if (dataProviderInterface.getName() === providerName) {
|
||||
return dataProviderInterface;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('No data provider has been found.');
|
||||
}
|
||||
|
||||
public getDataSourceForExchangeRates(): DataSource {
|
||||
return DataSource[
|
||||
this.configurationService.get('DATA_SOURCE_EXCHANGE_RATES')
|
||||
@ -520,20 +545,15 @@ export class DataProviderService {
|
||||
return { items: lookupItems };
|
||||
}
|
||||
|
||||
let dataSources = this.configurationService.get('DATA_SOURCES');
|
||||
|
||||
if (
|
||||
this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') &&
|
||||
user.subscription.type === 'Basic'
|
||||
) {
|
||||
dataSources = dataSources.filter((dataSource) => {
|
||||
return !this.isPremiumDataSource(DataSource[dataSource]);
|
||||
let dataProviderServices = this.configurationService
|
||||
.get('DATA_SOURCES')
|
||||
.map((dataSource) => {
|
||||
return this.getDataProvider(DataSource[dataSource]);
|
||||
});
|
||||
}
|
||||
|
||||
for (const dataSource of dataSources) {
|
||||
for (const dataProviderService of dataProviderServices) {
|
||||
promises.push(
|
||||
this.getDataProvider(DataSource[dataSource]).search({
|
||||
dataProviderService.search({
|
||||
includeIndices,
|
||||
query
|
||||
})
|
||||
@ -555,6 +575,16 @@ export class DataProviderService {
|
||||
})
|
||||
.sort(({ name: name1 }, { name: name2 }) => {
|
||||
return name1?.toLowerCase().localeCompare(name2?.toLowerCase());
|
||||
})
|
||||
.map((lookupItem) => {
|
||||
if (
|
||||
!this.configurationService.get('ENABLE_FEATURE_SUBSCRIPTION') ||
|
||||
user.subscription.type === 'Premium'
|
||||
) {
|
||||
lookupItem.dataProviderInfo.isPremium = false;
|
||||
}
|
||||
|
||||
return lookupItem;
|
||||
});
|
||||
|
||||
return {
|
||||
@ -562,31 +592,6 @@ export class DataProviderService {
|
||||
};
|
||||
}
|
||||
|
||||
private getDataProvider(providerName: DataSource) {
|
||||
for (const dataProviderInterface of this.dataProviderInterfaces) {
|
||||
if (this.dataProviderMapping[dataProviderInterface.getName()]) {
|
||||
const mappedDataProviderInterface = this.dataProviderInterfaces.find(
|
||||
(currentDataProviderInterface) => {
|
||||
return (
|
||||
currentDataProviderInterface.getName() ===
|
||||
this.dataProviderMapping[dataProviderInterface.getName()]
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
if (mappedDataProviderInterface) {
|
||||
return mappedDataProviderInterface;
|
||||
}
|
||||
}
|
||||
|
||||
if (dataProviderInterface.getName() === providerName) {
|
||||
return dataProviderInterface;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('No data provider has been found.');
|
||||
}
|
||||
|
||||
private hasCurrency({
|
||||
currency,
|
||||
dataGatheringItems
|
||||
@ -602,14 +607,6 @@ export class DataProviderService {
|
||||
});
|
||||
}
|
||||
|
||||
private isPremiumDataSource(aDataSource: DataSource) {
|
||||
const premiumDataSources: DataSource[] = [
|
||||
DataSource.EOD_HISTORICAL_DATA,
|
||||
DataSource.FINANCIAL_MODELING_PREP
|
||||
];
|
||||
return premiumDataSources.includes(aDataSource);
|
||||
}
|
||||
|
||||
private transformHistoricalData({
|
||||
allData,
|
||||
currency,
|
||||
|
@ -16,6 +16,7 @@ import {
|
||||
REPLACE_NAME_PARTS
|
||||
} from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT, isCurrency } from '@ghostfolio/common/helper';
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
AssetClass,
|
||||
@ -58,6 +59,12 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
||||
};
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: true
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({
|
||||
from,
|
||||
requestTimeout = this.configurationService.get('REQUEST_TIMEOUT'),
|
||||
@ -312,7 +319,8 @@ export class EodHistoricalDataService implements DataProviderInterface {
|
||||
dataSource,
|
||||
name,
|
||||
symbol,
|
||||
currency: this.convertCurrency(currency)
|
||||
currency: this.convertCurrency(currency),
|
||||
dataProviderInfo: this.getDataProviderInfo()
|
||||
};
|
||||
}
|
||||
)
|
||||
|
@ -45,6 +45,14 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
||||
};
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: true,
|
||||
name: 'Financial Modeling Prep',
|
||||
url: 'https://financialmodelingprep.com/developer/docs'
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({}: GetDividendsParams) {
|
||||
return {};
|
||||
}
|
||||
@ -202,11 +210,4 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
||||
|
||||
return { items };
|
||||
}
|
||||
|
||||
private getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
name: 'Financial Modeling Prep',
|
||||
url: 'https://financialmodelingprep.com/developer/docs'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
||||
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import { format } from 'date-fns';
|
||||
@ -40,6 +41,12 @@ export class GoogleSheetsService implements DataProviderInterface {
|
||||
};
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: false
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({}: GetDividendsParams) {
|
||||
return {};
|
||||
}
|
||||
@ -177,7 +184,11 @@ export class GoogleSheetsService implements DataProviderInterface {
|
||||
}
|
||||
});
|
||||
|
||||
return { items };
|
||||
return {
|
||||
items: items.map((item) => {
|
||||
return { ...item, dataProviderInfo: this.getDataProviderInfo() };
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
private async getSheet({
|
||||
|
@ -3,6 +3,7 @@ import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse
|
||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { Granularity } from '@ghostfolio/common/types';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
|
||||
@ -11,6 +12,8 @@ export interface DataProviderInterface {
|
||||
|
||||
getAssetProfile(aSymbol: string): Promise<Partial<SymbolProfile>>;
|
||||
|
||||
getDataProviderInfo(): DataProviderInfo;
|
||||
|
||||
getDividends({ from, granularity, symbol, to }: GetDividendsParams): Promise<{
|
||||
[date: string]: IDataProviderHistoricalResponse;
|
||||
}>;
|
||||
|
@ -18,7 +18,10 @@ import {
|
||||
extractNumberFromString,
|
||||
getYesterday
|
||||
} from '@ghostfolio/common/helper';
|
||||
import { ScraperConfiguration } from '@ghostfolio/common/interfaces';
|
||||
import {
|
||||
DataProviderInfo,
|
||||
ScraperConfiguration
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import * as cheerio from 'cheerio';
|
||||
@ -59,6 +62,12 @@ export class ManualService implements DataProviderInterface {
|
||||
return assetProfile;
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: false
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({}: GetDividendsParams) {
|
||||
return {};
|
||||
}
|
||||
@ -214,7 +223,11 @@ export class ManualService implements DataProviderInterface {
|
||||
return !isUUID(symbol);
|
||||
});
|
||||
|
||||
return { items };
|
||||
return {
|
||||
items: items.map((item) => {
|
||||
return { ...item, dataProviderInfo: this.getDataProviderInfo() };
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
public async test(scraperConfiguration: ScraperConfiguration) {
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import { format } from 'date-fns';
|
||||
@ -37,6 +38,12 @@ export class RapidApiService implements DataProviderInterface {
|
||||
};
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: false
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({}: GetDividendsParams) {
|
||||
return {};
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { DEFAULT_CURRENCY } from '@ghostfolio/common/config';
|
||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import { addDays, format, isSameDay } from 'date-fns';
|
||||
@ -47,6 +48,12 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
};
|
||||
}
|
||||
|
||||
public getDataProviderInfo(): DataProviderInfo {
|
||||
return {
|
||||
isPremium: false
|
||||
};
|
||||
}
|
||||
|
||||
public async getDividends({
|
||||
from,
|
||||
granularity = 'day',
|
||||
@ -283,6 +290,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
assetSubClass,
|
||||
symbol,
|
||||
currency: marketDataItem.currency,
|
||||
dataProviderInfo: this.getDataProviderInfo(),
|
||||
dataSource: this.getName(),
|
||||
name: this.yahooFinanceDataEnhancerService.formatName({
|
||||
longName: quote.longname,
|
||||
|
@ -1,4 +1,5 @@
|
||||
export interface DataProviderInfo {
|
||||
name: string;
|
||||
url: string;
|
||||
isPremium: boolean;
|
||||
name?: string;
|
||||
url?: string;
|
||||
}
|
||||
|
@ -129,8 +129,8 @@
|
||||
<th *matHeaderCellDef class="px-1" mat-header-cell>
|
||||
<ng-container i18n>Name</ng-container>
|
||||
</th>
|
||||
<td *matCellDef="let element" class="line-height-1 px-1" mat-cell>
|
||||
<div class="d-flex align-items-center">
|
||||
<td *matCellDef="let element" class="px-1" mat-cell>
|
||||
<div class="align-items-center d-flex line-height-1">
|
||||
<div>
|
||||
<span class="text-truncate">{{ element.SymbolProfile?.name }}</span>
|
||||
<span
|
||||
|
@ -15,12 +15,15 @@
|
||||
<mat-option
|
||||
*ngFor="let lookupItem of filteredLookupItems"
|
||||
class="line-height-1"
|
||||
[disabled]="lookupItem.dataProviderInfo.isPremium"
|
||||
[value]="lookupItem"
|
||||
>
|
||||
<span
|
||||
><b>{{ lookupItem.name }}</b></span
|
||||
>
|
||||
<br />
|
||||
<span class="align-items-center d-flex line-height-1"
|
||||
><b>{{ lookupItem.name }}</b>
|
||||
@if (lookupItem.dataProviderInfo.isPremium) {
|
||||
<gf-premium-indicator class="ml-1" [enableLink]="false" />
|
||||
}
|
||||
</span>
|
||||
<small class="text-muted"
|
||||
>{{ lookupItem.symbol | gfSymbol }} · {{ lookupItem.currency
|
||||
}}<ng-container *ngIf="lookupItem.assetSubClass">
|
||||
|
@ -7,6 +7,7 @@ import { MatInputModule } from '@angular/material/input';
|
||||
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
|
||||
import { SymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete/symbol-autocomplete.component';
|
||||
import { GfPremiumIndicatorModule } from '@ghostfolio/ui/premium-indicator';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SymbolAutocompleteComponent],
|
||||
@ -14,6 +15,7 @@ import { SymbolAutocompleteComponent } from '@ghostfolio/ui/symbol-autocomplete/
|
||||
imports: [
|
||||
CommonModule,
|
||||
FormsModule,
|
||||
GfPremiumIndicatorModule,
|
||||
GfSymbolModule,
|
||||
MatAutocompleteModule,
|
||||
MatFormFieldModule,
|
||||
|
Loading…
x
Reference in New Issue
Block a user