Add ability to search for indices and fix gf-symbol-autocomplete validation (#2094)

* Bugfix/Fix gf-symbol-autocomplete validation

* Feature/Add ability to search for indices

* Update changelog
pull/2104/head^2
Arghya Ghosh 1 year ago committed by GitHub
parent 51ca26bb4d
commit 6a802a62a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,16 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Added the ability to add an index for benchmarks as an asset profile in the admin control panel
### Fixed
- Fixed an issue with the clone functionality of a transaction caused by the symbol search component
## 1.283.5 - 2023-06-25 ## 1.283.5 - 2023-06-25
### Added ### Added

@ -36,10 +36,12 @@ export class SymbolController {
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
@UseInterceptors(TransformDataSourceInResponseInterceptor) @UseInterceptors(TransformDataSourceInResponseInterceptor)
public async lookupSymbol( public async lookupSymbol(
@Query() { query = '' } @Query('includeIndices') includeIndices: boolean = false,
@Query('query') query = ''
): Promise<{ items: LookupItem[] }> { ): Promise<{ items: LookupItem[] }> {
try { try {
return this.symbolService.lookup({ return this.symbolService.lookup({
includeIndices,
query: query.toLowerCase(), query: query.toLowerCase(),
user: this.request.user user: this.request.user
}); });

@ -81,9 +81,11 @@ export class SymbolService {
} }
public async lookup({ public async lookup({
includeIndices = false,
query, query,
user user
}: { }: {
includeIndices?: boolean;
query: string; query: string;
user: UserWithSettings; user: UserWithSettings;
}): Promise<{ items: LookupItem[] }> { }): Promise<{ items: LookupItem[] }> {
@ -95,6 +97,7 @@ export class SymbolService {
try { try {
const { items } = await this.dataProviderService.search({ const { items } = await this.dataProviderService.search({
includeIndices,
query, query,
user user
}); });

@ -114,8 +114,14 @@ export class AlphaVantageService implements DataProviderInterface {
return undefined; return undefined;
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
const result = await this.alphaVantage.data.search(aQuery); includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
const result = await this.alphaVantage.data.search(query);
return { return {
items: result?.bestMatches?.map((bestMatch) => { items: result?.bestMatches?.map((bestMatch) => {

@ -164,16 +164,17 @@ export class CoinGeckoService implements DataProviderInterface {
return 'bitcoin'; return 'bitcoin';
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
let items: LookupItem[] = []; let items: LookupItem[] = [];
try { try {
const get = bent( const get = bent(`${this.URL}/search?query=${query}`, 'GET', 'json', 200);
`${this.URL}/search?query=${aQuery}`,
'GET',
'json',
200
);
const { coins } = await get(); const { coins } = await get();
items = coins.map(({ id: symbol, name }) => { items = coins.map(({ id: symbol, name }) => {

@ -367,9 +367,11 @@ export class DataProviderService {
} }
public async search({ public async search({
includeIndices = false,
query, query,
user user
}: { }: {
includeIndices?: boolean;
query: string; query: string;
user: UserWithSettings; user: UserWithSettings;
}): Promise<{ items: LookupItem[] }> { }): Promise<{ items: LookupItem[] }> {
@ -392,7 +394,12 @@ export class DataProviderService {
} }
for (const dataSource of dataSources) { for (const dataSource of dataSources) {
promises.push(this.getDataProvider(DataSource[dataSource]).search(query)); promises.push(
this.getDataProvider(DataSource[dataSource]).search({
includeIndices,
query
})
);
} }
const searchResults = await Promise.all(promises); const searchResults = await Promise.all(promises);

@ -156,7 +156,7 @@ export class EodHistoricalDataService implements DataProviderInterface {
return !symbol.endsWith('.FOREX'); return !symbol.endsWith('.FOREX');
}) })
.map((symbol) => { .map((symbol) => {
return this.search(symbol); return this.search({ query: symbol });
}) })
); );
@ -219,8 +219,14 @@ export class EodHistoricalDataService implements DataProviderInterface {
return 'AAPL.US'; return 'AAPL.US';
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
const searchResult = await this.getSearchResult(aQuery); includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
const searchResult = await this.getSearchResult(query);
return { return {
items: searchResult items: searchResult

@ -143,12 +143,18 @@ export class FinancialModelingPrepService implements DataProviderInterface {
return 'AAPL'; return 'AAPL';
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
let items: LookupItem[] = []; let items: LookupItem[] = [];
try { try {
const get = bent( const get = bent(
`${this.URL}/search?query=${aQuery}&apikey=${this.apiKey}`, `${this.URL}/search?query=${query}&apikey=${this.apiKey}`,
'GET', 'GET',
'json', 'json',
200 200

@ -153,7 +153,13 @@ export class GoogleSheetsService implements DataProviderInterface {
return 'INDEXSP:.INX'; return 'INDEXSP:.INX';
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
const items = await this.prismaService.symbolProfile.findMany({ const items = await this.prismaService.symbolProfile.findMany({
select: { select: {
assetClass: true, assetClass: true,
@ -169,14 +175,14 @@ export class GoogleSheetsService implements DataProviderInterface {
dataSource: this.getName(), dataSource: this.getName(),
name: { name: {
mode: 'insensitive', mode: 'insensitive',
startsWith: aQuery startsWith: query
} }
}, },
{ {
dataSource: this.getName(), dataSource: this.getName(),
symbol: { symbol: {
mode: 'insensitive', mode: 'insensitive',
startsWith: aQuery startsWith: query
} }
} }
] ]

@ -42,5 +42,11 @@ export interface DataProviderInterface {
getTestSymbol(): string; getTestSymbol(): string;
search(aQuery: string): Promise<{ items: LookupItem[] }>; search({
includeIndices,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }>;
} }

@ -171,7 +171,13 @@ export class ManualService implements DataProviderInterface {
return undefined; return undefined;
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
let items = await this.prismaService.symbolProfile.findMany({ let items = await this.prismaService.symbolProfile.findMany({
select: { select: {
assetClass: true, assetClass: true,
@ -187,14 +193,14 @@ export class ManualService implements DataProviderInterface {
dataSource: this.getName(), dataSource: this.getName(),
name: { name: {
mode: 'insensitive', mode: 'insensitive',
startsWith: aQuery startsWith: query
} }
}, },
{ {
dataSource: this.getName(), dataSource: this.getName(),
symbol: { symbol: {
mode: 'insensitive', mode: 'insensitive',
startsWith: aQuery startsWith: query
} }
} }
] ]

@ -117,7 +117,13 @@ export class RapidApiService implements DataProviderInterface {
return undefined; return undefined;
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
return { items: [] }; return { items: [] };
} }

@ -275,11 +275,23 @@ export class YahooFinanceService implements DataProviderInterface {
return 'AAPL'; return 'AAPL';
} }
public async search(aQuery: string): Promise<{ items: LookupItem[] }> { public async search({
includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}): Promise<{ items: LookupItem[] }> {
const items: LookupItem[] = []; const items: LookupItem[] = [];
try { try {
const searchResult = await yahooFinance.search(aQuery); const quoteTypes = ['EQUITY', 'ETF', 'FUTURE', 'MUTUALFUND'];
if (includeIndices) {
quoteTypes.push('INDEX');
}
const searchResult = await yahooFinance.search(query);
const quotes = searchResult.quotes const quotes = searchResult.quotes
.filter((quote) => { .filter((quote) => {
@ -295,7 +307,7 @@ export class YahooFinanceService implements DataProviderInterface {
this.baseCurrency this.baseCurrency
) )
)) || )) ||
['EQUITY', 'ETF', 'FUTURE', 'MUTUALFUND'].includes(quoteType) quoteTypes.includes(quoteType)
); );
}) })
.filter(({ quoteType, symbol }) => { .filter(({ quoteType, symbol }) => {

@ -8,7 +8,10 @@
<div class="flex-grow-1 py-3" mat-dialog-content> <div class="flex-grow-1 py-3" mat-dialog-content>
<mat-form-field appearance="outline" class="w-100"> <mat-form-field appearance="outline" class="w-100">
<mat-label i18n>Name, symbol or ISIN</mat-label> <mat-label i18n>Name, symbol or ISIN</mat-label>
<gf-symbol-autocomplete formControlName="searchSymbol" /> <gf-symbol-autocomplete
formControlName="searchSymbol"
[includeIndices]="true"
/>
</mat-form-field> </mat-form-field>
</div> </div>
<div class="d-flex justify-content-end" mat-dialog-actions> <div class="d-flex justify-content-end" mat-dialog-actions>

@ -261,9 +261,21 @@ export class DataService {
}); });
} }
public fetchSymbols(aQuery: string) { public fetchSymbols({
includeIndices = false,
query
}: {
includeIndices?: boolean;
query: string;
}) {
let params = new HttpParams().set('query', query);
if (includeIndices) {
params = params.append('includeIndices', includeIndices);
}
return this.http return this.http
.get<{ items: LookupItem[] }>(`/api/v1/symbol/lookup?query=${aQuery}`) .get<{ items: LookupItem[] }>('/api/v1/symbol/lookup', { params })
.pipe( .pipe(
map((respose) => { map((respose) => {
return respose.items; return respose.items;

@ -50,6 +50,7 @@ export class SymbolAutocompleteComponent
extends AbstractMatFormField<LookupItem> extends AbstractMatFormField<LookupItem>
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
@Input() private includeIndices = false;
@Input() public isLoading = false; @Input() public isLoading = false;
@ViewChild(MatInput, { static: false }) private input: MatInput; @ViewChild(MatInput, { static: false }) private input: MatInput;
@ -94,7 +95,10 @@ export class SymbolAutocompleteComponent
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}), }),
switchMap((query: string) => { switchMap((query: string) => {
return this.dataService.fetchSymbols(query); return this.dataService.fetchSymbols({
query,
includeIndices: this.includeIndices
});
}) })
) )
.subscribe((filteredLookupItems) => { .subscribe((filteredLookupItems) => {
@ -132,7 +136,11 @@ export class SymbolAutocompleteComponent
public ngDoCheck() { public ngDoCheck() {
if (this.ngControl) { if (this.ngControl) {
this.validateRequired(); this.validateRequired();
this.validateSelection();
if (this.control.touched) {
this.validateSelection();
}
this.errorState = this.ngControl.invalid && this.ngControl.touched; this.errorState = this.ngControl.invalid && this.ngControl.touched;
this.stateChanges.next(); this.stateChanges.next();
} }

Loading…
Cancel
Save