Browse Source

Feature/extend search in assistant by accounts (#5356)

* Extend search in assistant by accounts

* Update changelog
pull/5370/head^2
David Requeno 3 days ago
committed by GitHub
parent
commit
473c474845
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 4
      CHANGELOG.md
  2. 17
      libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts
  3. 63
      libs/ui/src/lib/assistant/assistant.component.ts
  4. 28
      libs/ui/src/lib/assistant/assistant.html
  5. 1
      libs/ui/src/lib/assistant/enums/search-mode.ts
  6. 10
      libs/ui/src/lib/assistant/interfaces/interfaces.ts

4
CHANGELOG.md

@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Included accounts in the search results of the assistant
### Changed
- Migrated the prompt dialog component from `ngModel` to form control

17
libs/ui/src/lib/assistant/assistant-list-item/assistant-list-item.component.ts

@ -50,20 +50,27 @@ export class GfAssistantListItemComponent
public constructor(private changeDetectorRef: ChangeDetectorRef) {}
public ngOnChanges() {
if (this.item?.mode === SearchMode.ASSET_PROFILE) {
if (this.item?.mode === SearchMode.ACCOUNT) {
this.queryParams = {
accountDetailDialog: true,
accountId: this.item.id
};
this.routerLink = internalRoutes.accounts.routerLink;
} else if (this.item?.mode === SearchMode.ASSET_PROFILE) {
this.queryParams = {
assetProfileDialog: true,
dataSource: this.item?.dataSource,
symbol: this.item?.symbol
dataSource: this.item.dataSource,
symbol: this.item.symbol
};
this.routerLink =
internalRoutes.adminControl.subRoutes.marketData.routerLink;
} else if (this.item?.mode === SearchMode.HOLDING) {
this.queryParams = {
dataSource: this.item?.dataSource,
dataSource: this.item.dataSource,
holdingDetailDialog: true,
symbol: this.item?.symbol
symbol: this.item.symbol
};
this.routerLink = [];

63
libs/ui/src/lib/assistant/assistant.component.ts

@ -153,14 +153,16 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
});
public holdings: PortfolioPosition[] = [];
public isLoading = {
accounts: false,
assetProfiles: false,
holdings: false,
quickLinks: false
};
public isOpen = false;
public placeholder = $localize`Find holding or page...`;
public placeholder = $localize`Find account, holding or page...`;
public searchFormControl = new FormControl('');
public searchResults: ISearchResults = {
accounts: [],
assetProfiles: [],
holdings: [],
quickLinks: []
@ -199,11 +201,13 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
.pipe(
map((searchTerm) => {
this.isLoading = {
accounts: true,
assetProfiles: true,
holdings: true,
quickLinks: true
};
this.searchResults = {
accounts: [],
assetProfiles: [],
holdings: [],
quickLinks: []
@ -217,6 +221,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
distinctUntilChanged(),
switchMap((searchTerm) => {
const results = {
accounts: [],
assetProfiles: [],
holdings: [],
quickLinks: []
@ -226,6 +231,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
return of(results).pipe(
tap(() => {
this.isLoading = {
accounts: false,
assetProfiles: false,
holdings: false,
quickLinks: false
@ -234,6 +240,25 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
);
}
// Accounts
const accounts$: Observable<Partial<ISearchResults>> =
this.searchAccounts(searchTerm).pipe(
map((accounts) => ({
accounts: accounts.slice(
0,
GfAssistantComponent.SEARCH_RESULTS_DEFAULT_LIMIT
)
})),
catchError((error) => {
console.error('Error fetching accounts for assistant:', error);
return of({ accounts: [] as ISearchResultItem[] });
}),
tap(() => {
this.isLoading.accounts = false;
this.changeDetectorRef.markForCheck();
})
);
// Asset profiles
const assetProfiles$: Observable<Partial<ISearchResults>> = this
.hasPermissionToAccessAdminControl
@ -299,13 +324,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
);
// Merge all results
return merge(quickLinks$, assetProfiles$, holdings$).pipe(
return merge(accounts$, assetProfiles$, holdings$, quickLinks$).pipe(
scan(
(acc: ISearchResults, curr: Partial<ISearchResults>) => ({
...acc,
...curr
}),
{
accounts: [],
assetProfiles: [],
holdings: [],
quickLinks: []
@ -323,6 +349,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
error: (error) => {
console.error('Assistant search stream error:', error);
this.searchResults = {
accounts: [],
assetProfiles: [],
holdings: [],
quickLinks: []
@ -331,6 +358,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
},
complete: () => {
this.isLoading = {
accounts: false,
assetProfiles: false,
holdings: false,
quickLinks: false
@ -451,12 +479,14 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
public initialize() {
this.isLoading = {
accounts: true,
assetProfiles: true,
holdings: true,
quickLinks: true
};
this.keyManager = new FocusKeyManager(this.assistantListItems).withWrap();
this.searchResults = {
accounts: [],
assetProfiles: [],
holdings: [],
quickLinks: []
@ -472,6 +502,7 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
});
this.isLoading = {
accounts: false,
assetProfiles: false,
holdings: false,
quickLinks: false
@ -564,6 +595,34 @@ export class GfAssistantComponent implements OnChanges, OnDestroy, OnInit {
});
}
private searchAccounts(aSearchTerm: string): Observable<ISearchResultItem[]> {
return this.dataService
.fetchAccounts({
filters: [
{
id: aSearchTerm,
type: 'SEARCH_QUERY'
}
]
})
.pipe(
catchError(() => {
return EMPTY;
}),
map(({ accounts }) => {
return accounts.map(({ id, name }) => {
return {
id,
name,
routerLink: internalRoutes.accounts.routerLink,
mode: SearchMode.ACCOUNT as const
};
});
}),
takeUntil(this.unsubscribeSubject)
);
}
private searchAssetProfiles(
aSearchTerm: string
): Observable<ISearchResultItem[]> {

28
libs/ui/src/lib/assistant/assistant.html

@ -39,9 +39,11 @@
@if (searchFormControl.value) {
<div class="overflow-auto py-2 result-container">
@if (
!isLoading.accounts &&
!isLoading.assetProfiles &&
!isLoading.holdings &&
!isLoading.quickLinks &&
searchResults.accounts?.length === 0 &&
searchResults.assetProfiles?.length === 0 &&
searchResults.holdings?.length === 0 &&
searchResults.quickLinks?.length === 0
@ -76,6 +78,32 @@
}
</div>
}
@if (isLoading.accounts || searchResults?.accounts?.length !== 0) {
<div>
<div class="font-weight-bold px-3 text-muted title" i18n>
Accounts
</div>
@for (
searchResultItem of searchResults.accounts;
track searchResultItem
) {
<gf-assistant-list-item
[item]="searchResultItem"
(clicked)="onCloseAssistant()"
/>
}
@if (isLoading.accounts) {
<ngx-skeleton-loader
animation="pulse"
class="mx-3"
[theme]="{
height: '1.5rem',
width: '100%'
}"
/>
}
</div>
}
@if (isLoading.holdings || searchResults?.holdings?.length !== 0) {
<div>
<div class="font-weight-bold px-3 text-muted title" i18n>

1
libs/ui/src/lib/assistant/enums/search-mode.ts

@ -1,4 +1,5 @@
export enum SearchMode {
ACCOUNT = 'account',
ASSET_PROFILE = 'assetProfile',
HOLDING = 'holding',
QUICK_LINK = 'quickLink'

10
libs/ui/src/lib/assistant/interfaces/interfaces.ts

@ -1,8 +1,14 @@
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import { AccountWithValue, DateRange } from '@ghostfolio/common/types';
import { SearchMode } from '../enums/search-mode';
export interface IAccountSearchResultItem
extends Pick<AccountWithValue, 'id' | 'name'> {
mode: SearchMode.ACCOUNT;
routerLink: string[];
}
export interface IAssetSearchResultItem extends AssetProfileIdentifier {
assetSubClassString: string;
currency: string;
@ -22,10 +28,12 @@ export interface IQuickLinkSearchResultItem {
}
export type ISearchResultItem =
| IAccountSearchResultItem
| IAssetSearchResultItem
| IQuickLinkSearchResultItem;
export interface ISearchResults {
accounts: ISearchResultItem[];
assetProfiles: ISearchResultItem[];
holdings: ISearchResultItem[];
quickLinks: ISearchResultItem[];

Loading…
Cancel
Save